General setup

Setup chunk

Load libraries

knitr::opts_chunk$set(fig.width = 8)
knitr::opts_knit$set(root.dir = normalizePath(".."))
knitr::opts_knit$get("root.dir")
[1] "/nas/groups/treutlein/USERS/tomasgomes/projects/pallium_evo"

Set colours for cell types and regions

library(Seurat)
Attaching SeuratObject
library(ggplot2)
Learn more about the underlying theory at https://ggplot2-book.org/
library(Matrix)
library(mgcv)
Loading required package: nlme
This is mgcv 1.8-38. For overview type 'help("mgcv-package")'.
library(foreach)
library(doParallel)
Loading required package: iterators
Loading required package: parallel
library(parallel)

Prepare data

Load data

meta = read.csv("data/annotations/axolotl_all_umeta.csv", 
                header = T, row.names = 1)
cols_cc = c(
#epen
"#12400c", "#2d6624","#1d4f15", "#174711", "#2d6624", "#3d7f33", "#3b7b30", "#468b3b", "#4f9843","#5dae50", "#66bb58", "#72cd64", "#306a26", "#78d669", "#81e472",
#gaba
"#700209", "#75090e","#7a0f13", "#801517", "#851a1b", "#8a1f1f", "#902423", "#952927", "#9a2d2c","#a03230", "#a53634", "#aa3a39", "#b03f3d","#b54342", "#ba4846", "#c04c4b", "#c5504f", "#ca5554", "#d05959", "#d55e5e","#73050c", "#780c11","#8d2221", "#982b2a","#a23432", "#a83837", "#b2413f", "#b84544", "#bd4a49", "#c85352", #"#cd5756",
#glut
"#054674", "#134d7b","#1d5481", "#265a88", "#2e618e", "#73a4cb", "#366995", "#3e709c", "#4677a2","#4d7ea9", "#5586b0", "#5c8db7", "#6495bd","#6b9cc4", "#7bacd2", "#8ebfe4", "#96c7eb", "#9ecff2", "#18507e", "#18507e","#2a5e8b", "#497ba6","#5889b3", "#6fa0c8","#7fafd6", "#6091ba", "#5182ac", "#3a6c98", "#a6d7f9",
#npc
"#ffb120", "#feb72a","#fdbc34", "#fcc13d", "#fbc745", "#facc4e", "#f9d156", "#f8d65f", "#f8da68","#f7df70", "#f7e479", "#f7e882", "#f7ed8a", "#f7f193", "#eca319"
)
ccnames = unique(sort(meta$cellclusters))
names(cols_cc) = c(ccnames[grepl("epen", ccnames)], ccnames[grepl("GABA", ccnames)],ccnames[grepl("glut", ccnames)],ccnames[grepl("npc", ccnames)])

reg_cols = c("other/unknown_pred" = "#C7CCC7", 
             "medial" = "#52168D", "medial_pred" = "#661CB0", 
             "dorsal" = "#C56007", "dorsal_pred" = "#ED7307", 
             "lateral" = "#118392", "lateral_pred" = "#16A3B6")
reg_cols_simp = c("medial" = "#52168D", "dorsal" = "#C56007", "lateral" = "#118392")

Format metadata

ax_meta = ax_srat@meta.data[,c("classes", "cellclusters", "regions", "sample", "chem")]
ax_meta$sample = ifelse(endsWith(rownames(ax_meta), "-1_1"), "a1_1",
                 ifelse(endsWith(rownames(ax_meta), "-1_2"), "a1_2",
                 ifelse(endsWith(rownames(ax_meta), "-1_3"), "a3_1",
                 ifelse(endsWith(rownames(ax_meta), "-1_4"), "a3_2", ax_meta$sample))))

meta_regs = read.csv("data/processed/multiome/WP_region_predictions.csv", header = T, row.names = 1)
newcellnames = rownames(meta_regs)
newcellnames = gsub("-a1-1", "-1_1", newcellnames)
newcellnames = gsub("-a1-2", "-1_2", newcellnames)
newcellnames = gsub("-a3-1", "-1_3", newcellnames)
newcellnames = gsub("-a3-2", "-1_4", newcellnames)
rownames(meta_regs) = newcellnames
meta_regs$all_pred_regs_top = paste0(meta_regs$pred_regions_top, "_pred")
ax_meta = merge(ax_meta, meta_regs[,c(2,4)], by = 0, all = T)
ax_meta$pred_regions_top[is.na(ax_meta$pred_regions_top)] = ax_meta$regions[is.na(ax_meta$pred_regions_top)]
ax_meta$all_pred_regs_top[is.na(ax_meta$all_pred_regs_top)] = ax_meta$regions[is.na(ax_meta$all_pred_regs_top)]
rownames(ax_meta) = ax_meta[,1]
ax_meta = ax_meta[,-1]

ax_meta = cbind(ax_meta[rownames(ax_srat@reductions$umap_harmony@cell.embeddings),], 
                ax_srat@reductions$umap_harmony@cell.embeddings)
ax_meta = cbind(unlist(lapply(strsplit(rownames(ax_meta), "-"), function(x) x[1])), ax_meta)
colnames(ax_meta)[1] = "cells"

div_meta = div_srat@meta.data[,c("high_level_anno", "high_level_clustering", "sample", "batch")]
div_meta = cbind(div_meta, div_srat@reductions$umap@cell.embeddings)
div_meta = cbind(unlist(lapply(strsplit(rownames(div_meta), "-"), function(x) x[1])), div_meta)
colnames(div_meta)[1] = "cells"

Save metadata

write.csv(ax_meta, file = "data/annotations/pallium_meta_velocity.csv", row.names = T, quote = F)
write.csv(div_meta, file = "data/annotations/divseq_meta_velocity.csv", row.names = T, quote = F)

Steady-state neurogenesis

Load data

Load data

dir = "data/processed/velocity_results/glut_reg/"
meta_l = list()
umap_l = list()
abs_l = list()
ld_l = list()
g_l = list()
exp_l = list()
for(r in c("lat", "dor", "med", "all")){
  meta_l[[r]] = read.csv(paste0(dir, "glut_ss_", r, "_obs.csv"), header = T, row.names = 1)
  umap_l[[r]] = read.csv(paste0(dir, "glut_ss_", r, "_umap.csv"), header = T, row.names = 1)
  g_l[[r]] = read.csv(paste0(dir, "glut_ss_", r, "_var.csv"), header = T, row.names = 1)
  exp_l[[r]] = read.csv(paste0(dir, "glut_ss_", r, "_X.csv"), header = T, row.names = 1)
  
  if(r!="all"){
    abs_l[[r]] = read.csv(paste0(dir, "glut_ss_", r, "_abs_prob.csv"), header = T, row.names = 1)
    ld_l[[r]] = read.csv(paste0(dir, "glut_ss_", r, "_lineageDrivers.csv"), header = T, row.names = 1)
  }
}

metaNoEp_l = list()
umapNoEp_l = list()
absNoEp_l = list()
ldNoEp_l = list()
gNoEp_l = list()
expNoEp_l = list()
for(r in c("lat", "dor", "med")){
  metaNoEp_l[[r]] = read.csv(paste0(dir, "glutNoEp_ss_", r, "_obs.csv"), header = T, row.names = 1)
  umapNoEp_l[[r]] = read.csv(paste0(dir, "glutNoEp_ss_", r, "_umap.csv"), header = T, row.names = 1)
  gNoEp_l[[r]] = read.csv(paste0(dir, "glutNoEp_ss_", r, "_var.csv"), header = T, row.names = 1)
  
  if(r!="all"){
    absNoEp_l[[r]] = read.csv(paste0(dir, "glutNoEp_ss_", r, "_abs_prob.csv"), 
                              header = T, row.names = 1)
    ldNoEp_l[[r]] = read.csv(paste0(dir, "glutNoEp_ss_", r, "_lineageDrivers.csv"), 
                         header = T, row.names = 1)
    expNoEp_l[[r]] = read.csv(paste0(dir, "glutNoEp_ss_", r, "_X.csv"), header = T, row.names = 1)
  }
}

Pseudotime

Testing the changes to the pseudotime

reg = "med"
plot_df = cbind(umapNoEp_l[[reg]][rownames(absNoEp_l[[reg]]),],
                metaNoEp_l[[reg]][rownames(absNoEp_l[[reg]]),c("latent_time", "cellclusters")],
                absNoEp_l[[reg]])
plot_df$newpt = plot_df$latent_time*(1-plot_df$epen_clus_4)
plot_df$newpt2 = plot_df[,6]*(1-plot_df$epen_clus_4)
plot_df$newpt3 = apply(plot_df[,6:7], 1, max)*(1-plot_df$epen_clus_4)

ggplot(plot_df, aes(x = UMAP_1, y = UMAP_2, colour = latent_time))+
  geom_point(size = 2)+
  scale_colour_viridis_c(option = "E")+
  theme_classic()
ggplot(plot_df, aes(x = UMAP_1, y = UMAP_2, colour = newpt))+
  geom_point(size = 2)+
  scale_colour_viridis_c(option = "E")+
  theme_classic()
ggplot(plot_df, aes(x = UMAP_1, y = UMAP_2, colour = newpt3))+
  geom_point(size = 2)+
  scale_colour_viridis_c(option = "E")+
  theme_classic()
ggplot(plot_df, aes(x = UMAP_1, y = UMAP_2, colour = epen_clus_4))+
  geom_point(size = 2)+
  scale_colour_viridis_c(option = "E")+
  theme_classic()

Plot UMAP with fates

reg = "med"
plot_df = cbind(umapNoEp_l[[reg]][rownames(absNoEp_l[[reg]]),],
                metaNoEp_l[[reg]][rownames(absNoEp_l[[reg]]),c("latent_time", "cellclusters")],
                absNoEp_l[[reg]])
plot_df$newpt = plot_df$latent_time*(1-plot_df$epen_clus_4)
plot_df$newpt2 = plot_df[,6]*(1-plot_df$epen_clus_4)
plot_df$newpt3 = apply(plot_df[,6:7], 1, max)*(1-plot_df$epen_clus_4)

ggplot(plot_df, aes(x = UMAP_1, y = UMAP_2, colour = latent_time))+
  geom_point(size = 2)+
  scale_colour_viridis_c(option = "E")+
  theme_classic()

ggplot(plot_df, aes(x = UMAP_1, y = UMAP_2, colour = newpt))+
  geom_point(size = 2)+
  scale_colour_viridis_c(option = "E")+
  theme_classic()

ggplot(plot_df, aes(x = UMAP_1, y = UMAP_2, colour = newpt3))+
  geom_point(size = 2)+
  scale_colour_viridis_c(option = "E")+
  theme_classic()

ggplot(plot_df, aes(x = UMAP_1, y = UMAP_2, colour = epen_clus_4))+
  geom_point(size = 2)+
  scale_colour_viridis_c(option = "E")+
  theme_classic()

Make data frame with glutamatergic trajectories

umap_plt_list = list()
for(n in names(umapNoEp_l)){
  plot_df = cbind(umapNoEp_l[[n]][rownames(absNoEp_l[[n]]),],
                  metaNoEp_l[[n]][rownames(absNoEp_l[[n]]),c("cellclusters")],
                  absNoEp_l[[n]])
  colnames(plot_df)[3] = "cellclusters"
  
  for(cc in colnames(plot_df)[grepl("glut", colnames(plot_df))]){
    plot_df[,paste0(cc, "_transf")] = plot_df[,cc]*(1-plot_df$epen_clus_4)
  }
  
  plot_df$newpt =  apply(absNoEp_l[[n]][,grepl("glut", colnames(absNoEp_l[[n]]))], 1,
                         max)*(1-absNoEp_l[[n]]$epen_clus_4)
  
  plot_df = plot_df[,grepl("_transf", colnames(plot_df)) | 
                      grepl("newpt", colnames(plot_df)) |
                      grepl("UMAP", colnames(plot_df)) |
                      grepl("cellclusters", colnames(plot_df))]
  colnames(plot_df)[grepl("_transf", colnames(plot_df))] = gsub("_transf", "", colnames(plot_df)[grepl("_transf", colnames(plot_df))])
  
  umap_plt_list[[n]] = list()
  umap_plt_list[[n]][["cellclusters"]] = ggplot(mapping = aes(x = UMAP_1, y = UMAP_2))+
    geom_point(data = plot_df[order(plot_df$newpt, decreasing = T), ], 
               mapping = aes(colour = cellclusters), size = 0.3)+
    scale_colour_manual(values = cols_cc[names(cols_cc) %in% plot_df$cellclusters])+
    theme_classic()+
    theme(aspect.ratio = 1,
          axis.text = element_blank(),
          axis.title = element_blank(),
          axis.ticks = element_blank(),
          axis.line = element_blank())
  
  mean_df = data.frame("UMAP_1" = tapply(plot_df$UMAP_1, plot_df$cellclusters, mean),
                        "UMAP_2" = tapply(plot_df$UMAP_2, plot_df$cellclusters, mean),
                        "cellclusters" = levels(factor(plot_df$cellclusters)))
  umap_plt_list[[n]][["cellclusters_mean"]] = ggplot(mapping = aes(x = UMAP_1, y = UMAP_2))+
    geom_point(data = plot_df[order(plot_df$newpt, decreasing = T), ], 
               mapping = aes(colour = cellclusters), size = 0.3)+
    geom_text(data = mean_df, mapping = aes(label = cellclusters), fontface = "bold")+
    scale_colour_manual(values = cols_cc[names(cols_cc) %in% plot_df$cellclusters])+
    theme_classic()+
    theme(aspect.ratio = 1, legend.position = "none",
          axis.text = element_blank(),
          axis.title = element_blank(),
          axis.ticks = element_blank(),
          axis.line = element_blank())
  umap_plt_list[[n]][["newpt"]] = ggplot(mapping = aes(x = UMAP_1, y = UMAP_2))+
    geom_point(data = plot_df[order(plot_df$newpt, decreasing = T), ], 
               mapping = aes(colour = newpt), size = 0.3)+
    scale_colour_viridis_c(option = "C")+
    theme_classic()+
    theme(aspect.ratio = 1,
          axis.text = element_blank(),
          axis.title = element_blank(),
          axis.ticks = element_blank(),
          axis.line = element_blank())
  for(f in colnames(plot_df)[grepl("glut", colnames(plot_df))]){
    umap_plt_list[[n]][[f]] = ggplot(mapping = aes(x = UMAP_1, y = UMAP_2))+
      geom_point(data = plot_df[order(plot_df$newpt, decreasing = T), ], 
                 mapping = aes_string(colour = f), size = 0.3)+
      scale_colour_viridis_c(option = "C")+
      theme_classic()+
      theme(aspect.ratio = 1,
            axis.text = element_blank(),
            axis.title = element_blank(),
            axis.ticks = element_blank(),
            axis.line = element_blank())
  }
  
  for(f in names(umap_plt_list[[n]])){
    pdf(paste0("results/RNAvelocity/UMAP_regions/UMAP_", n, "_", f, ".pdf"), useDingbats = F, 
        height = 4, width = ifelse(f=="cellclusters", 6, 5))
    print(umap_plt_list[[n]][[f]])
    dev.off()
  }
}
*** recursive gc invocation
*** recursive gc invocation
*** recursive gc invocation
*** recursive gc invocation
*** recursive gc invocation
*** recursive gc invocation
*** recursive gc invocation
*** recursive gc invocation

Saving data (for use with multiome)

epfates = c("epen_clus_4")

tmp = list()
for(n in names(metaNoEp_l)[1:3]){
  fates = colnames(absNoEp_l[[n]])
  newpt =  apply(absNoEp_l[[n]][,grepl("glut", colnames(absNoEp_l[[n]]))], 1,
                 max)*(1-absNoEp_l[[n]]$epen_clus_4)
  fates = fates[!fates %in% epfates]
  for(f in fates){
    print(f)
    subabs = absNoEp_l[[n]][,!(colnames(absNoEp_l[[n]])==f |
                                 colnames(absNoEp_l[[n]]) %in% epfates)]
    rem = if(!is.null(dim(subabs))){
      apply(subabs, 1, function(x) any(x>=0.7))
    } else{
      apply(matrix(subabs), 1, function(x) any(x>=0.7))
    }
    tmp[[f]] = data.frame("cells" = rownames(metaNoEp_l[[n]]),
                          "orig_pt" = metaNoEp_l[[n]]$latent_time,
                          "newpt" = newpt,
                          "orig_prob" = absNoEp_l[[n]][,f],
                          "reg" = metaNoEp_l[[n]]$reg_simp,
                          "cellclusters" = metaNoEp_l[[n]]$cellclusters,
                          "fate" = f)
    tmp[[f]] = tmp[[f]][!rem,]
    tmp[[f]] = tmp[[f]][tmp[[f]]$orig_prob>=min(tmp[[f]]$orig_prob[tmp[[f]]$newpt==0]),]
    tmp[[f]]$pt = scales::rescale(tmp[[f]]$orig_pt, c(0,1))
    tmp[[f]]$prob = scales::rescale(tmp[[f]]$orig_prob, c(0,1))
  }
}
[1] "glut_SUBSET_10"
[1] "glut_SUBSET_2"
[1] "glut_SUBSET_22"
[1] "glut_SUBSET_1"
[1] "glut_SUBSET_3"
[1] "glut_SUBSET_0"
[1] "glut_SUBSET_11"
[1] "glut_SUBSET_13"
[1] "glut_SUBSET_7"
glut_dat_df = Reduce(rbind,tmp)

Cell type occupancy by bin, per lineage

newcellnames = glut_dat_df$cells
newcellnames = gsub("-a1_1", "-a1-1", newcellnames)
newcellnames = gsub("-a1_2", "-a1-2", newcellnames)
newcellnames = gsub("-a3_1", "-a3-1", newcellnames)
newcellnames = gsub("-a3_2", "-a3-2", newcellnames)
glut_dat_df$newcellnames = newcellnames

ref_glut_dat = glut_dat_df[,c(10,5,2,7,4,6,3,8,9)]
colnames(ref_glut_dat) = c("newcellnames", "region", "latent_time", "fate", "probability", 
                           "cellclusters", "new_pseudotime", "normalised_pseudotime",
                           "normalised_probability")
write.csv(ref_glut_dat, "results/RNAvelocity/ref_glut_dat.csv", 
          col.names = T, row.names = F, quote = F)
Warning in write.csv(ref_glut_dat, "results/RNAvelocity/ref_glut_dat.csv",  :
  attempt to set 'col.names' ignored

Cell type occupancy by bin, per region

col_prop_list = list()
smo_prop_list = list()
ct_all = list()
for(n in unique(glut_dat_df$fate)){
  # subset data
  submeta = glut_dat_df[glut_dat_df$fate==n,]
  lt_bins = cut(submeta$newpt, 100) # 100 equally-sized bins
  plot_df = data.frame(bins = lt_bins, 
                       cst = as.character(submeta$cellclusters))
  tab_df = table(plot_df$bins,plot_df$cst)
  
  # remove cell types that are too rare (<5%)
  tab_df = reshape2::melt(tab_df/rowSums(tab_df))
  usecl = tapply(tab_df$value, tab_df$Var2, function(x) any(x>0.05))
  plot_df = plot_df[plot_df$cst %in% names(usecl)[usecl],]
  tab_df = table(plot_df$bins,plot_df$cst)
  
  # normalise by cell type abundance
  med_w = prop.table(table(plot_df$cst))
  tab_df = t(apply(tab_df, 1, function(x) x/med_w[colnames(tab_df)]))
  
  # reshape
  tab_df = reshape2::melt(tab_df/rowSums(tab_df))
  tab_df$Var2 = as.character(tab_df$Var2)
  
  # prevent discontinuity by copying the previous column (likely not happening)
  tab_df = tab_df[order(tab_df$Var1, decreasing = F),]
  for(i in unique(tab_df$Var1)){
    if(any(is.nan(tab_df$value[tab_df$Var1==i]))){
      tab_df$value[tab_df$Var1==i] = prev
    }
    prev = tab_df$value[tab_df$Var1==i]
  }
  
  col_prop_list[[n]] = ggplot(tab_df, aes(x = Var1, y = value, fill = Var2))+
    geom_col()+
    scale_y_continuous(expand = c(0,0))+
    scale_fill_manual(values = cols_cc[names(cols_cc) %in% tab_df$Var2])+
    labs(x = "Bins", y = "Proportion", fill = "Cell type")+
    theme_classic()+
    theme(axis.text.x = element_blank(),
          axis.text.y = element_text(size = 6.5, colour = "black"),
          axis.ticks.x = element_blank(),
          axis.line = element_blank(),
          axis.title = element_text(size = 7),
          legend.text = element_text(size = 6),
          legend.title = element_text(size = 7),
          legend.key.size = unit(0.4, "cm"))
  
  # smoothen the proportions (and force constrain to 0-1)
  tab_df2 = tab_df
  tab_df2$value2 = tab_df2$value
  for(i in unique(tab_df2$Var2)){
    fff = loess(value~as.numeric(Var1), data = tab_df2[tab_df2$Var2==i,], 
                span = 0.5)
    pred = predict(fff)
    pred[pred>1] = 1
    pred[pred<0] = 0
    tab_df2$value2[tab_df2$Var2==i] = pred
  }
  
  # force constrain each interval to 0-1 by doing proportion
  for(i in unique(tab_df2$Var1)){
    tab_df2$value2[tab_df2$Var1==i] = tab_df2$value2[tab_df2$Var1==i]/sum(tab_df2$value2[tab_df2$Var1==i])
  }
  
  tab_df2$major = unlist(lapply(strsplit(tab_df2$Var2, "_"), function(x) x[1]))
  res = list()
  for(nnn in unique(tab_df2$Var1)){
    ss=tapply(tab_df2[tab_df2$Var1==nnn,"value2"], tab_df2[tab_df2$Var1==nnn,"major"], sum)
    res[[nnn]] = which.max(ss)
  }
  ct_all[[n]] = unlist(lapply(res, names))
  
  smo_prop_list[[n]] = ggplot(tab_df2, aes(x = Var1, y = value2, group = Var2, fill = Var2))+
    geom_area()+
    scale_y_continuous(expand = c(0,0))+
    scale_fill_manual(values = cols_cc[names(cols_cc) %in% tab_df2$Var2])+
    labs(x = "Bins", y = "Proportion", fill = "Cell type")+
    theme_classic()+
    theme(axis.text.x = element_blank(),
          axis.text.y = element_text(size = 6.5, colour = "black"),
          axis.ticks.x = element_blank(),
          axis.line = element_blank(),
          axis.title = element_text(size = 7),
          legend.text = element_text(size = 6),
          legend.title = element_text(size = 7),
          legend.key.size = unit(0.4, "cm"))
}

for(n in names(col_prop_list)){
pdf(paste0("results/RNAvelocity/prop_celltypes_traj_", n, ".pdf"), height = 2.6, width = 5)
  print(col_prop_list[[n]])
  dev.off()
}

for(n in names(col_prop_list)){
pdf(paste0("results/RNAvelocity/prop_celltypes_traj_", n, "_smooth.pdf"), height = 2.6, width = 5)
  print(smo_prop_list[[n]])
  dev.off()
}

Variable genes

Find all variable genes

smo_prop_list = list()
res_all = list() # determine max ct at each step
for(n in unique(glut_dat_df$reg)){
  # subset region
  submeta = glut_dat_df[glut_dat_df$reg==n,]
  lt_bins = cut(submeta$newpt, 100) #100 equally sized bins
  plot_df = data.frame(bins = lt_bins, 
                       cst = as.character(submeta$cellclusters))
  tab_df = table(plot_df$bins,plot_df$cst)
  
  # remove cell types that are too rare (<5%)
  tab_df = reshape2::melt(tab_df/rowSums(tab_df))
  usecl = tapply(tab_df$value, tab_df$Var2, function(x) any(x>0.05))
  plot_df = plot_df[plot_df$cst %in% names(usecl)[usecl],]
  tab_df = table(plot_df$bins,plot_df$cst)
  
  # normalise by cell type abundance
  #med_w = prop.table(table(plot_df$cst))
  #tab_df = t(apply(tab_df, 1, function(x) x/med_w[colnames(tab_df)]))
  # normalise by major cell type abundance
  med_w = prop.table(table(unlist(lapply(strsplit(plot_df$cst, "_"), function(x) x[1]))))
  orcol = colnames(tab_df)
  nn = unlist(lapply(strsplit(colnames(tab_df), "_"), function(x) x[1]))
  tab_df = t(apply(tab_df, 1, function(x) x/med_w[nn]))
  colnames(tab_df) = orcol
  
  # reshape
  tab_df = reshape2::melt(tab_df/rowSums(tab_df))
  tab_df$Var2 = as.character(tab_df$Var2)
  
  # prevent discontinuity by copying the previous column (likely not happening)
  tab_df = tab_df[order(tab_df$Var1, decreasing = F),]
  for(i in unique(tab_df$Var1)){
    if(any(is.nan(tab_df$value[tab_df$Var1==i]))){
      tab_df$value[tab_df$Var1==i] = prev
    }
    prev = tab_df$value[tab_df$Var1==i]
  }
  
  # smoothen the proportions (and force constrain to 0-1)
  tab_df2 = tab_df
  tab_df2$value2 = tab_df2$value
  for(i in unique(tab_df2$Var2)){
    fff = loess(value~as.numeric(Var1), data = tab_df2[tab_df2$Var2==i,], 
                span = 0.5)
    pred = predict(fff)
    pred[pred>1] = 1
    pred[pred<0] = 0
    tab_df2$value2[tab_df2$Var2==i] = pred
  }
  
  # force constrain each interval to 0-1 by doing proportion
  for(i in unique(tab_df2$Var1)){
    tab_df2$value2[tab_df2$Var1==i] = tab_df2$value2[tab_df2$Var1==i]/sum(tab_df2$value2[tab_df2$Var1==i])
  }
  
  tab_df2$major = unlist(lapply(strsplit(tab_df2$Var2, "_"), function(x) x[1]))
  res = list()
  for(nnn in unique(tab_df2$Var1)){
    ss=tapply(tab_df2[tab_df2$Var1==nnn,"value2"], tab_df2[tab_df2$Var1==nnn,"major"], sum)
    res[[nnn]] = which.max(ss)
  }
  res_all[[n]] = unlist(lapply(res, names))
  
  smo_prop_list[[n]] = ggplot(tab_df2, aes(x = Var1, y = value2, group = Var2, fill = Var2))+
    geom_area()+
    scale_y_continuous(expand = c(0,0))+
    scale_fill_manual(values = cols_cc[names(cols_cc) %in% tab_df2$Var2])+
    labs(x = "Bins", y = "Proportion", fill = "Cell type")+
    theme_classic()+
    theme(axis.text.x = element_blank(),
          axis.text.y = element_text(size = 6.5, colour = "black"),
          axis.ticks.x = element_blank(),
          axis.line = element_blank(),
          axis.title = element_text(size = 7),
          legend.text = element_text(size = 6),
          legend.title = element_text(size = 7),
          legend.key.size = unit(0.4, "cm"))
}

for(n in names(smo_prop_list)){
pdf(paste0("results/RNAvelocity/prop_celltypes_traj_region_", n, "_smooth.pdf"), 
    height = 2.6, width = 5)
  print(smo_prop_list[[n]])
  dev.off()
}

Prepare pvalue tables

registerDoParallel(40)

fitExp = function(g, mod_df) {
  res = list()
  cells = mod_df$cells
  mod_df$y = scale(exp_l$all[cells,g])
  
  m = gam(y~fate*splines::ns(newpt, df = 5)+0, weights = mod_df$prob, data = mod_df)
  p = mgcv::predict.gam(m, mod_df, type = "link", se.fit = TRUE)
  fits_df = data.frame("fit" = p$fit, "up_se" = p$fit+(2*p$se.fit), "lo_se" = p$fit-(2*p$se.fit))
  
  bin_df = data.frame("newpt" = rep(seq(0,1,length.out = 100), length(unique(unique(mod_df$fate)))),
                      "fate" = rep(unique(mod_df$fate), each = 100))
  p = predict(m, bin_df, type = "link", se.fit = TRUE)
  bin_df$fit = p$fit
  bin_df$up_se = p$fit+(2*p$se.fit)
  bin_df$lo_se = p$fit-(2*p$se.fit)
  
  res[["all_terms"]] = list("fits" = fits_df, "binned_fits" = bin_df, "pvals" = summary(m)$pTerms.pv)
  
  
  m = gam(y~reg*splines::ns(newpt, df = 5)+0, weights = mod_df$prob, data = mod_df)
  p = mgcv::predict.gam(m, mod_df, type = "link", se.fit = TRUE)
  fits_df = data.frame("fit" = p$fit, "up_se" = p$fit+(2*p$se.fit), "lo_se" = p$fit-(2*p$se.fit))
  
  bin_df = data.frame("newpt" = rep(seq(0,1,length.out = 100), length(unique(unique(mod_df$reg)))),
                      "reg" = rep(unique(mod_df$reg), each = 100))
  p = mgcv::predict.gam(m, bin_df, type = "link", se.fit = TRUE)
  bin_df$fit = p$fit
  bin_df$up_se = p$fit+(2*p$se.fit)
  bin_df$lo_se = p$fit-(2*p$se.fit)
  
  res[["region"]] = list("fits" = fits_df, "binned_fits" = bin_df, "pvals" = summary(m)$pTerms.pv)
  
  return(res)
}

ff = "results/RNAvelocity/SS_data_glutNoEp_fit_exp_everything.RDS"
if(file.exists(ff)){
  all_fit_exp = foreach(i=colnames(exp_l$all)) %dopar% {
    fitExp(i, glut_dat_df)
  }
  names(all_fit_exp) = colnames(exp_l$all)
  saveRDS(all_fit_exp, file = "results/RNAvelocity/SS_data_glutNoEp_fit_exp_everything.RDS")
} else{
  all_fit_exp = readRDS("results/RNAvelocity/SS_data_glutNoEp_fit_exp_everything.RDS")
}

Plotting example genes

pval_df = Reduce(rbind, lapply(all_fit_exp, function(x) x$all_terms$pvals))
rownames(pval_df) = names(all_fit_exp)

pval_reg_df = Reduce(rbind, lapply(all_fit_exp, function(x) x$region$pvals))
rownames(pval_reg_df) = names(all_fit_exp)

Variability per region

Find genes conserved and variable between regions

pltGene = function(g, dat, lab, sub = "all_terms"){
  plot_df = data.frame("pt" = dat[rownames(all_fit_exp[[g]][[sub]]$fits),"newpt"],
                       "reg" = dat[rownames(all_fit_exp[[g]][[sub]]$fits),lab],
                       "fit" = all_fit_exp[[g]][[sub]]$fits$fit,
                       "fit_up" = all_fit_exp[[g]][[sub]]$fits$up_se,
                       "fit_dn" = all_fit_exp[[g]][[sub]]$fits$lo_se)
  
  plt = ggplot(plot_df)+
    geom_line(mapping = aes(x = pt, y = fit, group = reg, colour = reg))+
    geom_ribbon(mapping = aes(x = pt, y = fit, group = reg, fill = reg, 
                              ymin = fit_dn, ymax = fit_up), alpha = 0.25)+
    scale_x_continuous(expand = c(0,0))+
    ggtitle(g)+
    theme_classic()+
    theme(aspect.ratio = 1)
  return(plt)
}

cowplot::plot_grid(
pltGene("KCNJ10", glut_dat_df, "fate")+theme(legend.position = "none"),
pltGene("SOX6", glut_dat_df, "fate")+theme(legend.position = "none"),
pltGene("GLI2", glut_dat_df, "fate")+theme(legend.position = "none"),
pltGene("MEX3A", glut_dat_df, "fate")+theme(legend.position = "none"),
pltGene("TOP2A", glut_dat_df, "fate")+theme(legend.position = "none"),
pltGene("SLC17A6", glut_dat_df, "fate")+theme(legend.position = "none"),
pltGene("ELMO1", glut_dat_df, "fate")+theme(legend.position = "none"),
pltGene("EOMES", glut_dat_df, "fate")+theme(legend.position = "none"),
ncol = 4, align = "hv")

Add peaking times

g_diff = rownames(pval_df)[pval_df[,3]<=0.05 & pval_reg_df[,3]<=0.05]
reg_groups = list("lateral" = c("glut_SUBSET_2", "glut_SUBSET_22", "glut_SUBSET_10"), 
                  "dorsal" = c("glut_SUBSET_3", "glut_SUBSET_1"), 
                  "medial" = c("glut_SUBSET_11", "glut_SUBSET_0",
                               "glut_SUBSET_7","glut_SUBSET_13"))

# get the minimum correlation per region across lineages
cor_g_list = list()
mean_g_list = list()
lin_binned_fits = list()
for(g in rownames(pval_df)){
  min_cors_g = c()
  mean_g_df = list()
  for(n in names(reg_groups)){
    plot_df = all_fit_exp[[g]]$all_terms$binned_fits
    plot_df = plot_df[plot_df$fate %in% reg_groups[[n]],]
    
    dat = data.frame(lapply(reg_groups[[n]], function(x) plot_df$fit[plot_df$fate==x]))
    colnames(dat) = reg_groups[[n]]
    cc = cor(dat, method = "sp")
    min_cors_g[[n]] = min(cc)
    
    mean_g_df[[n]] = apply(dat, 1, mean)
    
    lin_binned_fits[[g]] = if(n==names(reg_groups)[1]){
      dat
    } else{
      cbind(lin_binned_fits[[g]], dat)
    }
  }
  cor_g_list[[g]] = min_cors_g
  mean_g_list[[g]] = data.frame(mean_g_df)
}
cor_g = data.frame(Reduce(rbind, cor_g_list))
rownames(cor_g) = rownames(pval_df)

# get genes with no per-region correlation lower than 0.33
genes_agreeing_reg = apply(apply(cor_g, 2, function(x) x>=.3), 1, function(x) all(x))

# get minimum correlation across regions
min_cor_regs = list()
min_cor_lins = list()
reg_binned_fits = list()
for(g in names(genes_agreeing_reg)){
  plot_df = all_fit_exp[[g]]$region$binned_fits

  dat = data.frame(lapply(unique(plot_df$reg), function(x) plot_df$fit[plot_df$reg==x]))
  colnames(dat) = unique(plot_df$reg)
  reg_binned_fits[[g]] = dat

  cc = cor(dat, method = "sp")
  min_cor_regs[[g]] = min(cc)
  
  
  plot_df = all_fit_exp[[g]]$all_terms$binned_fits

  dat = data.frame(lapply(unique(plot_df$fate), function(x) plot_df$fit[plot_df$fate==x]))
  colnames(dat) = unique(plot_df$fate)

  cc = cor(dat, method = "sp")
  min_cor_lins[[g]] = min(cc)
}

# prepare table with all these results
region_result_dat = data.frame(row.names = rownames(pval_df),
                               "isVariable" = pval_df[,2]<=0.05 & pval_reg_df[,2]<=0.05,
                               "pval_variableLineage" = pval_df[,3],
                               "pval_variableRegion" = pval_reg_df[,3],
                               "variable_lineage_and_region" = pval_df[,3]<=0.05 &
                                 pval_reg_df[,3]<=0.05,
                               "agree_within_all_regions" = genes_agreeing_reg[rownames(pval_df)],
                               "min_corr_between_regions" = unlist(min_cor_regs)[rownames(pval_df)],
                               "min_corr_between_lineages" = unlist(min_cor_lins)[rownames(pval_df)])

region_result_dat$isCommonRegions = region_result_dat$min_corr_between_regions>.3 &
  region_result_dat$isVariable &
  region_result_dat$agree_within_all_regions
region_result_dat$isDiffRegions = region_result_dat$min_corr_between_regions<(-.3)

Save table

# adding max position across mean for all genes
mean_cor_g = lapply(rownames(region_result_dat), function(x) apply(reg_binned_fits[[x]], 1, mean))
names(mean_cor_g) = rownames(region_result_dat)
mean_cor_g = data.frame(Reduce(rbind, mean_cor_g))
rownames(mean_cor_g) = rownames(region_result_dat)
max_dat = apply(apply(mean_cor_g, 1, scales::rescale, to = c(0,1)), 2, function(x) which.max(x))
region_result_dat$maxPointAllReg = max_dat[rownames(region_result_dat)]

# max position per region
maxPointPerReg = lapply(rownames(region_result_dat), 
                        function(x) apply(reg_binned_fits[[x]], 2, 
                                          function(x) which.max(scales::rescale(x, to = c(0,1)))))
maxPointPerReg = Reduce(rbind, maxPointPerReg)
rownames(maxPointPerReg) = rownames(region_result_dat)
colnames(maxPointPerReg) = paste0("maxPointPerReg_", colnames(maxPointPerReg))
region_result_dat = cbind(region_result_dat, maxPointPerReg[rownames(region_result_dat),])

## classify it into  ependymal/npc/glut
aaa = lapply(colnames(maxPointPerReg), 
             function(x) res_all[[strsplit(x, "_")[[1]][2]]][maxPointPerReg[,x]])
names(aaa) = paste0("ctPosition_",
                    unlist(lapply(strsplit(colnames(maxPointPerReg), "_"), function(x) x[2])))
region_result_dat = cbind(region_result_dat, data.frame(aaa))

# max per lineage
maxPointPerLin = lapply(rownames(region_result_dat), 
                        function(x) apply(lin_binned_fits[[x]], 2, 
                                          function(x) which.max(scales::rescale(x, to = c(0,1)))))
maxPointPerLin = Reduce(rbind, maxPointPerLin)
rownames(maxPointPerLin) = rownames(region_result_dat)
colnames(maxPointPerLin) = paste0("maxPointPerLin_", colnames(maxPointPerLin))
region_result_dat = cbind(region_result_dat, maxPointPerLin[rownames(region_result_dat),])

## classify it into  ependymal/npc/glut
aaa = lapply(names(ct_all), 
             function(x) ct_all[[x]][maxPointPerLin[,paste0("maxPointPerLin_", x)]])
names(aaa) = paste0("ctPosition_", names(ct_all))
region_result_dat = cbind(region_result_dat, data.frame(aaa))

All genes common to all regions

# get genes correlating and not correlating across regions
cor_reg = rownames(region_result_dat)[region_result_dat$min_corr_between_regions>.3 &
                                        region_result_dat$isVariable &
                                        region_result_dat$agree_within_all_regions]

mean_cor_g = lapply(cor_reg, function(x) apply(reg_binned_fits[[x]], 1, mean))
names(mean_cor_g) = cor_reg

mean_cor_g = data.frame(Reduce(rbind, mean_cor_g))
rownames(mean_cor_g) = cor_reg

max_dat = apply(apply(mean_cor_g, 1, scales::rescale, to = c(0,1)), 2, function(x) which.max(x))
min_dat = apply(apply(mean_cor_g, 1, scales::rescale, to = c(0,1)), 2, function(x) sum(x>.9))
sc_dat = order(max_dat, min_dat, decreasing = c(F, F))

dat_norm = t(apply(mean_cor_g[sc_dat,], 1, scales::rescale, to = c(0,1)))

pdf("results/RNAvelocity/heatmap_gen/premade_plots/glutNoEp_panregion_heatmap_all.pdf", 
    height = 2.25, width = 2.5, useDingbats = F)
pheatmap::pheatmap(dat_norm, clustering_method = "ward.D", color = viridis::magma(100), 
                   border_color = NA, cluster_cols = F, cluster_rows = F,
                   show_colnames = F, show_rownames = F, angle_col = 0)
dev.off()

write.csv(dat_norm, file = "results/RNAvelocity/heatmap_gen/heatmap_dat_common_regions.csv",
          col.names = T, row.names = T, quote = F)

groups_genes = cut(max_dat, 5)
names(groups_genes) = names(max_dat)
groups_genes = sort(groups_genes)

go_l = list()
for(i in unique(groups_genes)){
  ggg = names(groups_genes)[groups_genes==i]
  go_l[[i]] = gprofiler2::gost(query = ggg[!grepl("AMEX", ggg)], organism = "hsapiens")$result
}
ggg = names(groups_genes)
go_l[["all"]] = gprofiler2::gost(query = ggg[!grepl("AMEX", ggg)], organism = "hsapiens")$result

TF and signalling

# get genes correlating and not correlating across regions
cor_reg = rownames(region_result_dat)[region_result_dat$min_corr_between_regions>.3 &
                                        region_result_dat$isVariable &
                                        region_result_dat$agree_within_all_regions]

mean_cor_g = lapply(cor_reg, function(x) apply(reg_binned_fits[[x]], 1, mean))
names(mean_cor_g) = cor_reg

mean_cor_g = data.frame(Reduce(rbind, mean_cor_g))
rownames(mean_cor_g) = cor_reg

max_dat = apply(apply(mean_cor_g, 1, scales::rescale, to = c(0,1)), 2, function(x) which.max(x))
min_dat = apply(apply(mean_cor_g, 1, scales::rescale, to = c(0,1)), 2, function(x) sum(x>.9))
sc_dat = order(max_dat, min_dat, decreasing = c(F, F))

dat_norm = t(apply(mean_cor_g[sc_dat,], 1, scales::rescale, to = c(0,1)))

pdf("results/RNAvelocity/heatmap_gen/premade_plots/glutNoEp_panregion_heatmap_all.pdf", 
    height = 2.25, width = 2.5, useDingbats = F)
pheatmap::pheatmap(dat_norm, clustering_method = "ward.D", color = viridis::magma(100), 
                   border_color = NA, cluster_cols = F, cluster_rows = F,
                   show_colnames = F, show_rownames = F, angle_col = 0)
dev.off()
null device 
          1 
write.csv(dat_norm, file = "results/RNAvelocity/heatmap_gen/heatmap_dat_common_regions.csv",
          col.names = T, row.names = T, quote = F)
Warning in write.csv(dat_norm, file = "results/RNAvelocity/heatmap_gen/heatmap_dat_common_regions.csv",  :
  attempt to set 'col.names' ignored
groups_genes = cut(max_dat, 5)
names(groups_genes) = names(max_dat)
groups_genes = sort(groups_genes)

go_l = list()
for(i in unique(groups_genes)){
  ggg = names(groups_genes)[groups_genes==i]
  go_l[[i]] = gprofiler2::gost(query = ggg[!grepl("AMEX", ggg)], organism = "hsapiens")$result
}
ggg = names(groups_genes)
go_l[["all"]] = gprofiler2::gost(query = ggg[!grepl("AMEX", ggg)], organism = "hsapiens")$result

Genes that are different for each region (by the region in which they are relevant)

human_tf = read.table("../../gene_refs/human/Homo_sapiens_TF.txt", header = T, sep = "\t")

resc_mat = dat_norm[rownames(dat_norm) %in% human_tf$Symbol,]
max_dat = apply(resc_mat, 1, function(x) which.max(x))
min_dat = apply(resc_mat, 1, function(x) sum(x>.9))
sc_dat = order(max_dat, min_dat, decreasing = c(F, F))

pdf("results/RNAvelocity/heatmap_gen/premade_plots/glutNoEp_panregion_heatmap_TF.pdf", 
    height = 2.25, width = 3, useDingbats = F)
pheatmap::pheatmap(resc_mat[sc_dat,], clustering_method = "ward.D", color = viridis::magma(100), 
                   border_color = NA, cluster_cols = F, cluster_rows = F, fontsize_row = 6.5,
                   show_colnames = F, show_rownames = T, angle_col = 0)
dev.off()
null device 
          1 
pdf("results/RNAvelocity/heatmap_gen/premade_plots/glutNoEp_panregion_heatmap_TF_long.pdf", 
    height = 8.5, width = 3, useDingbats = F)
pheatmap::pheatmap(resc_mat[sc_dat,], clustering_method = "ward.D", color = viridis::magma(100), 
                   border_color = NA, cluster_cols = F, cluster_rows = F, fontsize_row = 6.5,
                   show_colnames = F, show_rownames = T, angle_col = 0)
dev.off()
null device 
          1 
go = "GO:0007267"
cellcomm = gprofiler2::gconvert(go, target = "HGNC")
cellcomm = cellcomm$name[!cellcomm$name %in% human_tf$Symbol]
resc_mat = dat_norm[rownames(dat_norm) %in% cellcomm,]

max_dat = apply(resc_mat, 1, function(x) which.max(x))
min_dat = apply(resc_mat, 1, function(x) sum(x>.9))
sc_dat = order(max_dat, min_dat, decreasing = c(F, F))

pdf("results/RNAvelocity/heatmap_gen/premade_plots/glutNoEp_panregion_heatmap_signalling.pdf",
    height = 2.25, width = 3, useDingbats = F)
pheatmap::pheatmap(resc_mat[sc_dat,], clustering_method = "ward.D", color = viridis::magma(100), 
                   border_color = NA, cluster_cols = F, cluster_rows = F,
                   show_colnames = F, show_rownames = T, angle_col = 0)
dev.off()
null device 
          1 
pdf("results/RNAvelocity/heatmap_gen/premade_plots/glutNoEp_panregion_heatmap_signalling_long.pdf",
    height = 9, width = 3, useDingbats = F)
pheatmap::pheatmap(resc_mat[sc_dat,], clustering_method = "ward.D", color = viridis::magma(100), 
                   border_color = NA, cluster_cols = F, cluster_rows = F, fontsize_row = 6.5,
                   show_colnames = F, show_rownames = T, angle_col = 0)
dev.off()
null device 
          1 

TF and signalling

human_tf = read.table("../../gene_refs/human/Homo_sapiens_TF.txt", header = T, sep = "\t")
for(r in names(sorted_dat)){
  resc_mat = sorted_dat[[r]][rownames(sorted_dat[[r]]) %in% human_tf$Symbol,]
  
  max_dat = apply(resc_mat, 1, function(x) which.max(x))
  min_dat = apply(resc_mat, 1, function(x) sum(x>.9))
  sc_dat = order(max_dat, min_dat, decreasing = c(F, F))
  
  pdf(paste0("results/RNAvelocity/heatmap_gen/premade_plots/glutNoEp_", r, "_heatmap_TF.pdf"), height = 2.25, width = 3, useDingbats = F)
  pheatmap::pheatmap(resc_mat[sc_dat,], clustering_method = "ward.D", border_color = NA,
                     color = viridis::magma(100), cluster_cols = F, cluster_rows = F, fontsize_row = 7,
                     show_colnames = F, show_rownames = T)
  dev.off()
  
  pdf(paste0("results/RNAvelocity/heatmap_gen/premade_plots/glutNoEp_", r, "_heatmap_TF_long.pdf"), height = 9, width = 3, useDingbats = F)
  pheatmap::pheatmap(resc_mat[sc_dat,], clustering_method = "ward.D", border_color = NA,
                     color = viridis::magma(100), cluster_cols = F, cluster_rows = F, fontsize_row = 7,
                     show_colnames = F, show_rownames = T)
  dev.off()
}

go = "GO:0007267"
cellcomm = gprofiler2::gconvert(go, target = "HGNC")
cellcomm = cellcomm$name[!cellcomm$name %in% human_tf$Symbol]

signl = read.csv("../../gene_refs/human/CellPhoneDB/gene_input.csv", header = T)
signl = unique(c(signl$gene_name[!signl$gene_name %in% human_tf$Symbol],cellcomm))

for(r in names(sorted_dat)){
  resc_mat = sorted_dat[[r]][rownames(sorted_dat[[r]]) %in% signl,]
  
  max_dat = apply(resc_mat, 1, function(x) which.max(x))
  min_dat = apply(resc_mat, 1, function(x) sum(x>.9))
  sc_dat = order(max_dat, min_dat, decreasing = c(F, F))
  
  pdf(paste0("results/RNAvelocity/heatmap_gen/premade_plots/glutNoEp_", r,
             "_heatmap_signalling.pdf"), height = 2.25, width = 3, useDingbats = F)
  pheatmap::pheatmap(resc_mat[sc_dat,], clustering_method = "ward.D", fontsize_row = 7,
                     color = viridis::magma(100), cluster_cols = F, cluster_rows = F, 
                     show_colnames = F, show_rownames = T, border_color = NA)
  dev.off()
  
  pdf(paste0("results/RNAvelocity/heatmap_gen/premade_plots/glutNoEp_", r,
             "_heatmap_signalling_long.pdf"), height = 17, width = 3, useDingbats = F)
  pheatmap::pheatmap(resc_mat[sc_dat,], clustering_method = "ward.D", fontsize_row = 7,
                     color = viridis::magma(100), cluster_cols = F, cluster_rows = F, 
                     show_colnames = F, show_rownames = T, border_color = NA)
  dev.off()
}

Plot individual genes

human_tf = read.table("../../gene_refs/human/Homo_sapiens_TF.txt", header = T, sep = "\t")
for(r in names(sorted_dat)){
  resc_mat = sorted_dat[[r]][rownames(sorted_dat[[r]]) %in% human_tf$Symbol,]
  
  max_dat = apply(resc_mat, 1, function(x) which.max(x))
  min_dat = apply(resc_mat, 1, function(x) sum(x>.9))
  sc_dat = order(max_dat, min_dat, decreasing = c(F, F))
  
  pdf(paste0("results/RNAvelocity/heatmap_gen/premade_plots/glutNoEp_", r, "_heatmap_TF.pdf"), height = 2.25, width = 3, useDingbats = F)
  pheatmap::pheatmap(resc_mat[sc_dat,], clustering_method = "ward.D", border_color = NA,
                     color = viridis::magma(100), cluster_cols = F, cluster_rows = F, fontsize_row = 7,
                     show_colnames = F, show_rownames = T)
  dev.off()
  
  pdf(paste0("results/RNAvelocity/heatmap_gen/premade_plots/glutNoEp_", r, "_heatmap_TF_long.pdf"), height = 9, width = 3, useDingbats = F)
  pheatmap::pheatmap(resc_mat[sc_dat,], clustering_method = "ward.D", border_color = NA,
                     color = viridis::magma(100), cluster_cols = F, cluster_rows = F, fontsize_row = 7,
                     show_colnames = F, show_rownames = T)
  dev.off()
}

go = "GO:0007267"
cellcomm = gprofiler2::gconvert(go, target = "HGNC")
cellcomm = cellcomm$name[!cellcomm$name %in% human_tf$Symbol]

signl = read.csv("../../gene_refs/human/CellPhoneDB/gene_input.csv", header = T)
signl = unique(c(signl$gene_name[!signl$gene_name %in% human_tf$Symbol],cellcomm))

for(r in names(sorted_dat)){
  resc_mat = sorted_dat[[r]][rownames(sorted_dat[[r]]) %in% signl,]
  
  max_dat = apply(resc_mat, 1, function(x) which.max(x))
  min_dat = apply(resc_mat, 1, function(x) sum(x>.9))
  sc_dat = order(max_dat, min_dat, decreasing = c(F, F))
  
  pdf(paste0("results/RNAvelocity/heatmap_gen/premade_plots/glutNoEp_", r,
             "_heatmap_signalling.pdf"), height = 2.25, width = 3, useDingbats = F)
  pheatmap::pheatmap(resc_mat[sc_dat,], clustering_method = "ward.D", fontsize_row = 7,
                     color = viridis::magma(100), cluster_cols = F, cluster_rows = F, 
                     show_colnames = F, show_rownames = T, border_color = NA)
  dev.off()
  
  pdf(paste0("results/RNAvelocity/heatmap_gen/premade_plots/glutNoEp_", r,
             "_heatmap_signalling_long.pdf"), height = 17, width = 3, useDingbats = F)
  pheatmap::pheatmap(resc_mat[sc_dat,], clustering_method = "ward.D", fontsize_row = 7,
                     color = viridis::magma(100), cluster_cols = F, cluster_rows = F, 
                     show_colnames = F, show_rownames = T, border_color = NA)
  dev.off()
}
pltRegGene = function(g, dat, col, group, sub = "all_terms"){
  plot_df = data.frame("pt" = dat[rownames(all_fit_exp[[g]][[sub]]$fits),"newpt"],
                       "col" = dat[rownames(all_fit_exp[[g]][[sub]]$fits),col],
                       "group" = dat[rownames(all_fit_exp[[g]][[sub]]$fits),group],
                       "fit" = all_fit_exp[[g]][[sub]]$fits$fit,
                       "fit_up" = all_fit_exp[[g]][[sub]]$fits$up_se,
                       "fit_dn" = all_fit_exp[[g]][[sub]]$fits$lo_se)
  
  plt = ggplot(plot_df)+
    geom_line(mapping = aes(x = pt, y = fit, group = group, colour = col))+
    geom_ribbon(mapping = aes(x = pt, y = fit, group = group, fill = col, 
                              ymin = fit_dn, ymax = fit_up), alpha = 0.25)+
    scale_x_continuous(expand = c(0,0))+
    ggtitle(g)+
    theme_classic()+
    theme(aspect.ratio = 1,
          axis.text = element_text(colour = "black", size = 5),
          title = element_text(size = 6.75))
  return(plt)
}


genes_to_show = c("TOP2A", "GLI2", "SLC17A6", "NEUROD2", "BCL11B", 
                  "PRDM2", "E2F8", "NRG1", "NRG3", "FGF14", "WNT7B", 
                  "KCNJ10", "NOTCH1", "FGFR2", "SEMA4F", "NEURL1", 
                  "MEX3A", "EOMES", "NEUROD4", "POU2F2", "MEF2D",
                  "ONECUT1", "FOXN3", "GLIS2", "ZBTB10", "MEF2A",
                  "ZBTB41", "NFX1")

lineage_genes = lapply(ld_l, function(x) lapply(colnames(x)[grepl("corr", colnames(x))], 
                                                function(y) rownames(x)[order(x[,y], decreasing = T)][1:10]))

genes_to_show = unique(c(genes_to_show, unlist(lineage_genes)))

plt_ind_shared = list()
for(g in genes_to_show){
  f = paste0("results/RNAvelocity/individual_gene_kinetics/glutNoEp_", g, ".pdf")
  if(!file.exists(f)){
    f_plt = pltRegGene(g, glut_dat_df, "reg", "fate")+NoLegend()
    r_plt = pltRegGene(g, glut_dat_df, "reg", "reg", "region")+NoLegend()+
      theme(title = element_blank())
    
    r_plt = r_plt+
      scale_colour_manual(values = reg_cols_simp)+
      scale_fill_manual(values = reg_cols_simp)
    f_plt = f_plt+
      scale_colour_manual(values = reg_cols_simp)+
      scale_fill_manual(values = reg_cols_simp)
      
    plt_ind_shared[[g]] = cowplot::plot_grid(f_plt, r_plt, nrow = 1, align = "hv")
    
    pdf(f, height = 3, width = 4.5)
    print(plt_ind_shared[[g]])
    dev.off()
  }
}
LS0tCnRpdGxlOiAiUk5BIHZlbG9jaXR5IgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKLS0tCgojIEdlbmVyYWwgc2V0dXAKU2V0dXAgY2h1bmsKCmBgYHtyLCBzZXR1cCwgaW5jbHVkZT1GQUxTRX0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGZpZy53aWR0aCA9IDgpCmtuaXRyOjpvcHRzX2tuaXQkc2V0KHJvb3QuZGlyID0gbm9ybWFsaXplUGF0aCgiLi4iKSkKa25pdHI6Om9wdHNfa25pdCRnZXQoInJvb3QuZGlyIikKYGBgCgpMb2FkIGxpYnJhcmllcwoKYGBge3J9CmxpYnJhcnkoU2V1cmF0KQpsaWJyYXJ5KGdncGxvdDIpCmxpYnJhcnkoTWF0cml4KQpsaWJyYXJ5KG1nY3YpCmxpYnJhcnkoZm9yZWFjaCkKbGlicmFyeShkb1BhcmFsbGVsKQpsaWJyYXJ5KHBhcmFsbGVsKQpgYGAKClNldCBjb2xvdXJzIGZvciBjZWxsIHR5cGVzIGFuZCByZWdpb25zCgpgYGB7cn0KbWV0YSA9IHJlYWQuY3N2KCJkYXRhL2Fubm90YXRpb25zL2F4b2xvdGxfYWxsX3VtZXRhLmNzdiIsIAogICAgICAgICAgICAgICAgaGVhZGVyID0gVCwgcm93Lm5hbWVzID0gMSkKY29sc19jYyA9IGMoCiNlcGVuCiIjMTI0MDBjIiwgIiMyZDY2MjQiLCIjMWQ0ZjE1IiwgIiMxNzQ3MTEiLCAiIzJkNjYyNCIsICIjM2Q3ZjMzIiwgIiMzYjdiMzAiLCAiIzQ2OGIzYiIsICIjNGY5ODQzIiwiIzVkYWU1MCIsICIjNjZiYjU4IiwgIiM3MmNkNjQiLCAiIzMwNmEyNiIsICIjNzhkNjY5IiwgIiM4MWU0NzIiLAojZ2FiYQoiIzcwMDIwOSIsICIjNzUwOTBlIiwiIzdhMGYxMyIsICIjODAxNTE3IiwgIiM4NTFhMWIiLCAiIzhhMWYxZiIsICIjOTAyNDIzIiwgIiM5NTI5MjciLCAiIzlhMmQyYyIsIiNhMDMyMzAiLCAiI2E1MzYzNCIsICIjYWEzYTM5IiwgIiNiMDNmM2QiLCIjYjU0MzQyIiwgIiNiYTQ4NDYiLCAiI2MwNGM0YiIsICIjYzU1MDRmIiwgIiNjYTU1NTQiLCAiI2QwNTk1OSIsICIjZDU1ZTVlIiwiIzczMDUwYyIsICIjNzgwYzExIiwiIzhkMjIyMSIsICIjOTgyYjJhIiwiI2EyMzQzMiIsICIjYTgzODM3IiwgIiNiMjQxM2YiLCAiI2I4NDU0NCIsICIjYmQ0YTQ5IiwgIiNjODUzNTIiLCAjIiNjZDU3NTYiLAojZ2x1dAoiIzA1NDY3NCIsICIjMTM0ZDdiIiwiIzFkNTQ4MSIsICIjMjY1YTg4IiwgIiMyZTYxOGUiLCAiIzczYTRjYiIsICIjMzY2OTk1IiwgIiMzZTcwOWMiLCAiIzQ2NzdhMiIsIiM0ZDdlYTkiLCAiIzU1ODZiMCIsICIjNWM4ZGI3IiwgIiM2NDk1YmQiLCIjNmI5Y2M0IiwgIiM3YmFjZDIiLCAiIzhlYmZlNCIsICIjOTZjN2ViIiwgIiM5ZWNmZjIiLCAiIzE4NTA3ZSIsICIjMTg1MDdlIiwiIzJhNWU4YiIsICIjNDk3YmE2IiwiIzU4ODliMyIsICIjNmZhMGM4IiwiIzdmYWZkNiIsICIjNjA5MWJhIiwgIiM1MTgyYWMiLCAiIzNhNmM5OCIsICIjYTZkN2Y5IiwKI25wYwoiI2ZmYjEyMCIsICIjZmViNzJhIiwiI2ZkYmMzNCIsICIjZmNjMTNkIiwgIiNmYmM3NDUiLCAiI2ZhY2M0ZSIsICIjZjlkMTU2IiwgIiNmOGQ2NWYiLCAiI2Y4ZGE2OCIsIiNmN2RmNzAiLCAiI2Y3ZTQ3OSIsICIjZjdlODgyIiwgIiNmN2VkOGEiLCAiI2Y3ZjE5MyIsICIjZWNhMzE5IgopCmNjbmFtZXMgPSB1bmlxdWUoc29ydChtZXRhJGNlbGxjbHVzdGVycykpCm5hbWVzKGNvbHNfY2MpID0gYyhjY25hbWVzW2dyZXBsKCJlcGVuIiwgY2NuYW1lcyldLCBjY25hbWVzW2dyZXBsKCJHQUJBIiwgY2NuYW1lcyldLGNjbmFtZXNbZ3JlcGwoImdsdXQiLCBjY25hbWVzKV0sY2NuYW1lc1tncmVwbCgibnBjIiwgY2NuYW1lcyldKQoKcmVnX2NvbHMgPSBjKCJvdGhlci91bmtub3duX3ByZWQiID0gIiNDN0NDQzciLCAKICAgICAgICAgICAgICJtZWRpYWwiID0gIiM1MjE2OEQiLCAibWVkaWFsX3ByZWQiID0gIiM2NjFDQjAiLCAKICAgICAgICAgICAgICJkb3JzYWwiID0gIiNDNTYwMDciLCAiZG9yc2FsX3ByZWQiID0gIiNFRDczMDciLCAKICAgICAgICAgICAgICJsYXRlcmFsIiA9ICIjMTE4MzkyIiwgImxhdGVyYWxfcHJlZCIgPSAiIzE2QTNCNiIpCnJlZ19jb2xzX3NpbXAgPSBjKCJtZWRpYWwiID0gIiM1MjE2OEQiLCAiZG9yc2FsIiA9ICIjQzU2MDA3IiwgImxhdGVyYWwiID0gIiMxMTgzOTIiKQpgYGAKCgoKIyBQcmVwYXJlIGRhdGEKTG9hZCBkYXRhCgpgYGB7cn0KYXhfc3JhdCA9IHJlYWRSRFMoImRhdGEvZXhwcmVzc2lvbi9heG9sb3RsX3JlY2x1c3QvYWxsX251Y2xlaV9jbHVzdGVyZWRfaGlnaGxldmVsX2Fubm8uUkRTIikKbWV0YSA9IHJlYWQuY3N2KCJkYXRhL2Fubm90YXRpb25zL2F4b2xvdGxfYWxsX3VtZXRhLmNzdiIsIAogICAgICAgICAgICAgICAgaGVhZGVyID0gVCwgcm93Lm5hbWVzID0gMSkKYXhfc3JhdCA9IEFkZE1ldGFEYXRhKGF4X3NyYXQsIG1ldGFkYXRhID0gbWV0YSkKCmRpdl9zcmF0ID0gcmVhZFJEUygiZGF0YS9leHByZXNzaW9uL2F4b2xvdGxfcmVjbHVzdC9FZHVfMV8yXzRfNl84XzEyX2ZpbF9oaWdodmFyZmVhdC5SRFMiKQpgYGAKCkZvcm1hdCBtZXRhZGF0YQoKYGBge3J9CmF4X21ldGEgPSBheF9zcmF0QG1ldGEuZGF0YVssYygiY2xhc3NlcyIsICJjZWxsY2x1c3RlcnMiLCAicmVnaW9ucyIsICJzYW1wbGUiLCAiY2hlbSIpXQpheF9tZXRhJHNhbXBsZSA9IGlmZWxzZShlbmRzV2l0aChyb3duYW1lcyhheF9tZXRhKSwgIi0xXzEiKSwgImExXzEiLAogICAgICAgICAgICAgICAgIGlmZWxzZShlbmRzV2l0aChyb3duYW1lcyhheF9tZXRhKSwgIi0xXzIiKSwgImExXzIiLAogICAgICAgICAgICAgICAgIGlmZWxzZShlbmRzV2l0aChyb3duYW1lcyhheF9tZXRhKSwgIi0xXzMiKSwgImEzXzEiLAogICAgICAgICAgICAgICAgIGlmZWxzZShlbmRzV2l0aChyb3duYW1lcyhheF9tZXRhKSwgIi0xXzQiKSwgImEzXzIiLCBheF9tZXRhJHNhbXBsZSkpKSkKCm1ldGFfcmVncyA9IHJlYWQuY3N2KCJkYXRhL3Byb2Nlc3NlZC9tdWx0aW9tZS9XUF9yZWdpb25fcHJlZGljdGlvbnMuY3N2IiwgaGVhZGVyID0gVCwgcm93Lm5hbWVzID0gMSkKbmV3Y2VsbG5hbWVzID0gcm93bmFtZXMobWV0YV9yZWdzKQpuZXdjZWxsbmFtZXMgPSBnc3ViKCItYTEtMSIsICItMV8xIiwgbmV3Y2VsbG5hbWVzKQpuZXdjZWxsbmFtZXMgPSBnc3ViKCItYTEtMiIsICItMV8yIiwgbmV3Y2VsbG5hbWVzKQpuZXdjZWxsbmFtZXMgPSBnc3ViKCItYTMtMSIsICItMV8zIiwgbmV3Y2VsbG5hbWVzKQpuZXdjZWxsbmFtZXMgPSBnc3ViKCItYTMtMiIsICItMV80IiwgbmV3Y2VsbG5hbWVzKQpyb3duYW1lcyhtZXRhX3JlZ3MpID0gbmV3Y2VsbG5hbWVzCm1ldGFfcmVncyRhbGxfcHJlZF9yZWdzX3RvcCA9IHBhc3RlMChtZXRhX3JlZ3MkcHJlZF9yZWdpb25zX3RvcCwgIl9wcmVkIikKYXhfbWV0YSA9IG1lcmdlKGF4X21ldGEsIG1ldGFfcmVnc1ssYygyLDQpXSwgYnkgPSAwLCBhbGwgPSBUKQpheF9tZXRhJHByZWRfcmVnaW9uc190b3BbaXMubmEoYXhfbWV0YSRwcmVkX3JlZ2lvbnNfdG9wKV0gPSBheF9tZXRhJHJlZ2lvbnNbaXMubmEoYXhfbWV0YSRwcmVkX3JlZ2lvbnNfdG9wKV0KYXhfbWV0YSRhbGxfcHJlZF9yZWdzX3RvcFtpcy5uYShheF9tZXRhJGFsbF9wcmVkX3JlZ3NfdG9wKV0gPSBheF9tZXRhJHJlZ2lvbnNbaXMubmEoYXhfbWV0YSRhbGxfcHJlZF9yZWdzX3RvcCldCnJvd25hbWVzKGF4X21ldGEpID0gYXhfbWV0YVssMV0KYXhfbWV0YSA9IGF4X21ldGFbLC0xXQoKYXhfbWV0YSA9IGNiaW5kKGF4X21ldGFbcm93bmFtZXMoYXhfc3JhdEByZWR1Y3Rpb25zJHVtYXBfaGFybW9ueUBjZWxsLmVtYmVkZGluZ3MpLF0sIAogICAgICAgICAgICAgICAgYXhfc3JhdEByZWR1Y3Rpb25zJHVtYXBfaGFybW9ueUBjZWxsLmVtYmVkZGluZ3MpCmF4X21ldGEgPSBjYmluZCh1bmxpc3QobGFwcGx5KHN0cnNwbGl0KHJvd25hbWVzKGF4X21ldGEpLCAiLSIpLCBmdW5jdGlvbih4KSB4WzFdKSksIGF4X21ldGEpCmNvbG5hbWVzKGF4X21ldGEpWzFdID0gImNlbGxzIgoKZGl2X21ldGEgPSBkaXZfc3JhdEBtZXRhLmRhdGFbLGMoImhpZ2hfbGV2ZWxfYW5ubyIsICJoaWdoX2xldmVsX2NsdXN0ZXJpbmciLCAic2FtcGxlIiwgImJhdGNoIildCmRpdl9tZXRhID0gY2JpbmQoZGl2X21ldGEsIGRpdl9zcmF0QHJlZHVjdGlvbnMkdW1hcEBjZWxsLmVtYmVkZGluZ3MpCmRpdl9tZXRhID0gY2JpbmQodW5saXN0KGxhcHBseShzdHJzcGxpdChyb3duYW1lcyhkaXZfbWV0YSksICItIiksIGZ1bmN0aW9uKHgpIHhbMV0pKSwgZGl2X21ldGEpCmNvbG5hbWVzKGRpdl9tZXRhKVsxXSA9ICJjZWxscyIKYGBgCgpTYXZlIG1ldGFkYXRhCgpgYGB7cn0Kd3JpdGUuY3N2KGF4X21ldGEsIGZpbGUgPSAiZGF0YS9hbm5vdGF0aW9ucy9wYWxsaXVtX21ldGFfdmVsb2NpdHkuY3N2Iiwgcm93Lm5hbWVzID0gVCwgcXVvdGUgPSBGKQp3cml0ZS5jc3YoZGl2X21ldGEsIGZpbGUgPSAiZGF0YS9hbm5vdGF0aW9ucy9kaXZzZXFfbWV0YV92ZWxvY2l0eS5jc3YiLCByb3cubmFtZXMgPSBULCBxdW90ZSA9IEYpCmBgYAoKCgojIFN0ZWFkeS1zdGF0ZSBuZXVyb2dlbmVzaXMKIyMgTG9hZCBkYXRhCkxvYWQgZGF0YQoKYGBge3J9CmRpciA9ICJkYXRhL3Byb2Nlc3NlZC92ZWxvY2l0eV9yZXN1bHRzL2dsdXRfcmVnLyIKbWV0YV9sID0gbGlzdCgpCnVtYXBfbCA9IGxpc3QoKQphYnNfbCA9IGxpc3QoKQpsZF9sID0gbGlzdCgpCmdfbCA9IGxpc3QoKQpleHBfbCA9IGxpc3QoKQpmb3IociBpbiBjKCJsYXQiLCAiZG9yIiwgIm1lZCIsICJhbGwiKSl7CiAgbWV0YV9sW1tyXV0gPSByZWFkLmNzdihwYXN0ZTAoZGlyLCAiZ2x1dF9zc18iLCByLCAiX29icy5jc3YiKSwgaGVhZGVyID0gVCwgcm93Lm5hbWVzID0gMSkKICB1bWFwX2xbW3JdXSA9IHJlYWQuY3N2KHBhc3RlMChkaXIsICJnbHV0X3NzXyIsIHIsICJfdW1hcC5jc3YiKSwgaGVhZGVyID0gVCwgcm93Lm5hbWVzID0gMSkKICBnX2xbW3JdXSA9IHJlYWQuY3N2KHBhc3RlMChkaXIsICJnbHV0X3NzXyIsIHIsICJfdmFyLmNzdiIpLCBoZWFkZXIgPSBULCByb3cubmFtZXMgPSAxKQogIGV4cF9sW1tyXV0gPSByZWFkLmNzdihwYXN0ZTAoZGlyLCAiZ2x1dF9zc18iLCByLCAiX1guY3N2IiksIGhlYWRlciA9IFQsIHJvdy5uYW1lcyA9IDEpCiAgCiAgaWYociE9ImFsbCIpewogICAgYWJzX2xbW3JdXSA9IHJlYWQuY3N2KHBhc3RlMChkaXIsICJnbHV0X3NzXyIsIHIsICJfYWJzX3Byb2IuY3N2IiksIGhlYWRlciA9IFQsIHJvdy5uYW1lcyA9IDEpCiAgICBsZF9sW1tyXV0gPSByZWFkLmNzdihwYXN0ZTAoZGlyLCAiZ2x1dF9zc18iLCByLCAiX2xpbmVhZ2VEcml2ZXJzLmNzdiIpLCBoZWFkZXIgPSBULCByb3cubmFtZXMgPSAxKQogIH0KfQoKbWV0YU5vRXBfbCA9IGxpc3QoKQp1bWFwTm9FcF9sID0gbGlzdCgpCmFic05vRXBfbCA9IGxpc3QoKQpsZE5vRXBfbCA9IGxpc3QoKQpnTm9FcF9sID0gbGlzdCgpCmV4cE5vRXBfbCA9IGxpc3QoKQpmb3IociBpbiBjKCJsYXQiLCAiZG9yIiwgIm1lZCIpKXsKICBtZXRhTm9FcF9sW1tyXV0gPSByZWFkLmNzdihwYXN0ZTAoZGlyLCAiZ2x1dE5vRXBfc3NfIiwgciwgIl9vYnMuY3N2IiksIGhlYWRlciA9IFQsIHJvdy5uYW1lcyA9IDEpCiAgdW1hcE5vRXBfbFtbcl1dID0gcmVhZC5jc3YocGFzdGUwKGRpciwgImdsdXROb0VwX3NzXyIsIHIsICJfdW1hcC5jc3YiKSwgaGVhZGVyID0gVCwgcm93Lm5hbWVzID0gMSkKICBnTm9FcF9sW1tyXV0gPSByZWFkLmNzdihwYXN0ZTAoZGlyLCAiZ2x1dE5vRXBfc3NfIiwgciwgIl92YXIuY3N2IiksIGhlYWRlciA9IFQsIHJvdy5uYW1lcyA9IDEpCiAgCiAgaWYociE9ImFsbCIpewogICAgYWJzTm9FcF9sW1tyXV0gPSByZWFkLmNzdihwYXN0ZTAoZGlyLCAiZ2x1dE5vRXBfc3NfIiwgciwgIl9hYnNfcHJvYi5jc3YiKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGhlYWRlciA9IFQsIHJvdy5uYW1lcyA9IDEpCiAgICBsZE5vRXBfbFtbcl1dID0gcmVhZC5jc3YocGFzdGUwKGRpciwgImdsdXROb0VwX3NzXyIsIHIsICJfbGluZWFnZURyaXZlcnMuY3N2IiksIAogICAgICAgICAgICAgICAgICAgICAgICAgaGVhZGVyID0gVCwgcm93Lm5hbWVzID0gMSkKICAgIGV4cE5vRXBfbFtbcl1dID0gcmVhZC5jc3YocGFzdGUwKGRpciwgImdsdXROb0VwX3NzXyIsIHIsICJfWC5jc3YiKSwgaGVhZGVyID0gVCwgcm93Lm5hbWVzID0gMSkKICB9Cn0KYGBgCgoKIyMgUHNldWRvdGltZQpUZXN0aW5nIHRoZSBjaGFuZ2VzIHRvIHRoZSBwc2V1ZG90aW1lCgpgYGB7cn0KcmVnID0gIm1lZCIKcGxvdF9kZiA9IGNiaW5kKHVtYXBOb0VwX2xbW3JlZ11dW3Jvd25hbWVzKGFic05vRXBfbFtbcmVnXV0pLF0sCiAgICAgICAgICAgICAgICBtZXRhTm9FcF9sW1tyZWddXVtyb3duYW1lcyhhYnNOb0VwX2xbW3JlZ11dKSxjKCJsYXRlbnRfdGltZSIsICJjZWxsY2x1c3RlcnMiKV0sCiAgICAgICAgICAgICAgICBhYnNOb0VwX2xbW3JlZ11dKQpwbG90X2RmJG5ld3B0ID0gcGxvdF9kZiRsYXRlbnRfdGltZSooMS1wbG90X2RmJGVwZW5fY2x1c180KQpwbG90X2RmJG5ld3B0MiA9IHBsb3RfZGZbLDZdKigxLXBsb3RfZGYkZXBlbl9jbHVzXzQpCnBsb3RfZGYkbmV3cHQzID0gYXBwbHkocGxvdF9kZlssNjo3XSwgMSwgbWF4KSooMS1wbG90X2RmJGVwZW5fY2x1c180KQoKZ2dwbG90KHBsb3RfZGYsIGFlcyh4ID0gVU1BUF8xLCB5ID0gVU1BUF8yLCBjb2xvdXIgPSBsYXRlbnRfdGltZSkpKwogIGdlb21fcG9pbnQoc2l6ZSA9IDIpKwogIHNjYWxlX2NvbG91cl92aXJpZGlzX2Mob3B0aW9uID0gIkUiKSsKICB0aGVtZV9jbGFzc2ljKCkKZ2dwbG90KHBsb3RfZGYsIGFlcyh4ID0gVU1BUF8xLCB5ID0gVU1BUF8yLCBjb2xvdXIgPSBuZXdwdCkpKwogIGdlb21fcG9pbnQoc2l6ZSA9IDIpKwogIHNjYWxlX2NvbG91cl92aXJpZGlzX2Mob3B0aW9uID0gIkUiKSsKICB0aGVtZV9jbGFzc2ljKCkKZ2dwbG90KHBsb3RfZGYsIGFlcyh4ID0gVU1BUF8xLCB5ID0gVU1BUF8yLCBjb2xvdXIgPSBuZXdwdDMpKSsKICBnZW9tX3BvaW50KHNpemUgPSAyKSsKICBzY2FsZV9jb2xvdXJfdmlyaWRpc19jKG9wdGlvbiA9ICJFIikrCiAgdGhlbWVfY2xhc3NpYygpCmdncGxvdChwbG90X2RmLCBhZXMoeCA9IFVNQVBfMSwgeSA9IFVNQVBfMiwgY29sb3VyID0gZXBlbl9jbHVzXzQpKSsKICBnZW9tX3BvaW50KHNpemUgPSAyKSsKICBzY2FsZV9jb2xvdXJfdmlyaWRpc19jKG9wdGlvbiA9ICJFIikrCiAgdGhlbWVfY2xhc3NpYygpCmBgYAoKUGxvdCBVTUFQIHdpdGggZmF0ZXMKCmBgYHtyfQp1bWFwX3BsdF9saXN0ID0gbGlzdCgpCmZvcihuIGluIG5hbWVzKHVtYXBOb0VwX2wpKXsKICBwbG90X2RmID0gY2JpbmQodW1hcE5vRXBfbFtbbl1dW3Jvd25hbWVzKGFic05vRXBfbFtbbl1dKSxdLAogICAgICAgICAgICAgICAgICBtZXRhTm9FcF9sW1tuXV1bcm93bmFtZXMoYWJzTm9FcF9sW1tuXV0pLGMoImNlbGxjbHVzdGVycyIpXSwKICAgICAgICAgICAgICAgICAgYWJzTm9FcF9sW1tuXV0pCiAgY29sbmFtZXMocGxvdF9kZilbM10gPSAiY2VsbGNsdXN0ZXJzIgogIAogIGZvcihjYyBpbiBjb2xuYW1lcyhwbG90X2RmKVtncmVwbCgiZ2x1dCIsIGNvbG5hbWVzKHBsb3RfZGYpKV0pewogICAgcGxvdF9kZlsscGFzdGUwKGNjLCAiX3RyYW5zZiIpXSA9IHBsb3RfZGZbLGNjXSooMS1wbG90X2RmJGVwZW5fY2x1c180KQogIH0KICAKICBwbG90X2RmJG5ld3B0ID0gIGFwcGx5KGFic05vRXBfbFtbbl1dWyxncmVwbCgiZ2x1dCIsIGNvbG5hbWVzKGFic05vRXBfbFtbbl1dKSldLCAxLAogICAgICAgICAgICAgICAgICAgICAgICAgbWF4KSooMS1hYnNOb0VwX2xbW25dXSRlcGVuX2NsdXNfNCkKICAKICBwbG90X2RmID0gcGxvdF9kZlssZ3JlcGwoIl90cmFuc2YiLCBjb2xuYW1lcyhwbG90X2RmKSkgfCAKICAgICAgICAgICAgICAgICAgICAgIGdyZXBsKCJuZXdwdCIsIGNvbG5hbWVzKHBsb3RfZGYpKSB8CiAgICAgICAgICAgICAgICAgICAgICBncmVwbCgiVU1BUCIsIGNvbG5hbWVzKHBsb3RfZGYpKSB8CiAgICAgICAgICAgICAgICAgICAgICBncmVwbCgiY2VsbGNsdXN0ZXJzIiwgY29sbmFtZXMocGxvdF9kZikpXQogIGNvbG5hbWVzKHBsb3RfZGYpW2dyZXBsKCJfdHJhbnNmIiwgY29sbmFtZXMocGxvdF9kZikpXSA9IGdzdWIoIl90cmFuc2YiLCAiIiwgY29sbmFtZXMocGxvdF9kZilbZ3JlcGwoIl90cmFuc2YiLCBjb2xuYW1lcyhwbG90X2RmKSldKQogIAogIHVtYXBfcGx0X2xpc3RbW25dXSA9IGxpc3QoKQogIHVtYXBfcGx0X2xpc3RbW25dXVtbImNlbGxjbHVzdGVycyJdXSA9IGdncGxvdChtYXBwaW5nID0gYWVzKHggPSBVTUFQXzEsIHkgPSBVTUFQXzIpKSsKICAgIGdlb21fcG9pbnQoZGF0YSA9IHBsb3RfZGZbb3JkZXIocGxvdF9kZiRuZXdwdCwgZGVjcmVhc2luZyA9IFQpLCBdLCAKICAgICAgICAgICAgICAgbWFwcGluZyA9IGFlcyhjb2xvdXIgPSBjZWxsY2x1c3RlcnMpLCBzaXplID0gMC4zKSsKICAgIHNjYWxlX2NvbG91cl9tYW51YWwodmFsdWVzID0gY29sc19jY1tuYW1lcyhjb2xzX2NjKSAlaW4lIHBsb3RfZGYkY2VsbGNsdXN0ZXJzXSkrCiAgICB0aGVtZV9jbGFzc2ljKCkrCiAgICB0aGVtZShhc3BlY3QucmF0aW8gPSAxLAogICAgICAgICAgYXhpcy50ZXh0ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgYXhpcy50aXRsZSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgIGF4aXMudGlja3MgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICBheGlzLmxpbmUgPSBlbGVtZW50X2JsYW5rKCkpCiAgCiAgbWVhbl9kZiA9IGRhdGEuZnJhbWUoIlVNQVBfMSIgPSB0YXBwbHkocGxvdF9kZiRVTUFQXzEsIHBsb3RfZGYkY2VsbGNsdXN0ZXJzLCBtZWFuKSwKICAgICAgICAgICAgICAgICAgICAgICAgIlVNQVBfMiIgPSB0YXBwbHkocGxvdF9kZiRVTUFQXzIsIHBsb3RfZGYkY2VsbGNsdXN0ZXJzLCBtZWFuKSwKICAgICAgICAgICAgICAgICAgICAgICAgImNlbGxjbHVzdGVycyIgPSBsZXZlbHMoZmFjdG9yKHBsb3RfZGYkY2VsbGNsdXN0ZXJzKSkpCiAgdW1hcF9wbHRfbGlzdFtbbl1dW1siY2VsbGNsdXN0ZXJzX21lYW4iXV0gPSBnZ3Bsb3QobWFwcGluZyA9IGFlcyh4ID0gVU1BUF8xLCB5ID0gVU1BUF8yKSkrCiAgICBnZW9tX3BvaW50KGRhdGEgPSBwbG90X2RmW29yZGVyKHBsb3RfZGYkbmV3cHQsIGRlY3JlYXNpbmcgPSBUKSwgXSwgCiAgICAgICAgICAgICAgIG1hcHBpbmcgPSBhZXMoY29sb3VyID0gY2VsbGNsdXN0ZXJzKSwgc2l6ZSA9IDAuMykrCiAgICBnZW9tX3RleHQoZGF0YSA9IG1lYW5fZGYsIG1hcHBpbmcgPSBhZXMobGFiZWwgPSBjZWxsY2x1c3RlcnMpLCBmb250ZmFjZSA9ICJib2xkIikrCiAgICBzY2FsZV9jb2xvdXJfbWFudWFsKHZhbHVlcyA9IGNvbHNfY2NbbmFtZXMoY29sc19jYykgJWluJSBwbG90X2RmJGNlbGxjbHVzdGVyc10pKwogICAgdGhlbWVfY2xhc3NpYygpKwogICAgdGhlbWUoYXNwZWN0LnJhdGlvID0gMSwgbGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiLAogICAgICAgICAgYXhpcy50ZXh0ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgYXhpcy50aXRsZSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgIGF4aXMudGlja3MgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICBheGlzLmxpbmUgPSBlbGVtZW50X2JsYW5rKCkpCiAgdW1hcF9wbHRfbGlzdFtbbl1dW1sibmV3cHQiXV0gPSBnZ3Bsb3QobWFwcGluZyA9IGFlcyh4ID0gVU1BUF8xLCB5ID0gVU1BUF8yKSkrCiAgICBnZW9tX3BvaW50KGRhdGEgPSBwbG90X2RmW29yZGVyKHBsb3RfZGYkbmV3cHQsIGRlY3JlYXNpbmcgPSBUKSwgXSwgCiAgICAgICAgICAgICAgIG1hcHBpbmcgPSBhZXMoY29sb3VyID0gbmV3cHQpLCBzaXplID0gMC4zKSsKICAgIHNjYWxlX2NvbG91cl92aXJpZGlzX2Mob3B0aW9uID0gIkMiKSsKICAgIHRoZW1lX2NsYXNzaWMoKSsKICAgIHRoZW1lKGFzcGVjdC5yYXRpbyA9IDEsCiAgICAgICAgICBheGlzLnRleHQgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICBheGlzLnRpdGxlID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgYXhpcy50aWNrcyA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgIGF4aXMubGluZSA9IGVsZW1lbnRfYmxhbmsoKSkKICBmb3IoZiBpbiBjb2xuYW1lcyhwbG90X2RmKVtncmVwbCgiZ2x1dCIsIGNvbG5hbWVzKHBsb3RfZGYpKV0pewogICAgdW1hcF9wbHRfbGlzdFtbbl1dW1tmXV0gPSBnZ3Bsb3QobWFwcGluZyA9IGFlcyh4ID0gVU1BUF8xLCB5ID0gVU1BUF8yKSkrCiAgICAgIGdlb21fcG9pbnQoZGF0YSA9IHBsb3RfZGZbb3JkZXIocGxvdF9kZiRuZXdwdCwgZGVjcmVhc2luZyA9IFQpLCBdLCAKICAgICAgICAgICAgICAgICBtYXBwaW5nID0gYWVzX3N0cmluZyhjb2xvdXIgPSBmKSwgc2l6ZSA9IDAuMykrCiAgICAgIHNjYWxlX2NvbG91cl92aXJpZGlzX2Mob3B0aW9uID0gIkMiKSsKICAgICAgdGhlbWVfY2xhc3NpYygpKwogICAgICB0aGVtZShhc3BlY3QucmF0aW8gPSAxLAogICAgICAgICAgICBheGlzLnRleHQgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICAgIGF4aXMudGl0bGUgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICAgIGF4aXMudGlja3MgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICAgIGF4aXMubGluZSA9IGVsZW1lbnRfYmxhbmsoKSkKICB9CiAgCiAgZm9yKGYgaW4gbmFtZXModW1hcF9wbHRfbGlzdFtbbl1dKSl7CiAgICBwZGYocGFzdGUwKCJyZXN1bHRzL1JOQXZlbG9jaXR5L1VNQVBfcmVnaW9ucy9VTUFQXyIsIG4sICJfIiwgZiwgIi5wZGYiKSwgdXNlRGluZ2JhdHMgPSBGLCAKICAgICAgICBoZWlnaHQgPSA0LCB3aWR0aCA9IGlmZWxzZShmPT0iY2VsbGNsdXN0ZXJzIiwgNiwgNSkpCiAgICBwcmludCh1bWFwX3BsdF9saXN0W1tuXV1bW2ZdXSkKICAgIGRldi5vZmYoKQogIH0KfQpgYGAKCk1ha2UgZGF0YSBmcmFtZSB3aXRoIGdsdXRhbWF0ZXJnaWMgdHJhamVjdG9yaWVzCgpgYGB7cn0KZXBmYXRlcyA9IGMoImVwZW5fY2x1c180IikKCnRtcCA9IGxpc3QoKQpmb3IobiBpbiBuYW1lcyhtZXRhTm9FcF9sKVsxOjNdKXsKICBmYXRlcyA9IGNvbG5hbWVzKGFic05vRXBfbFtbbl1dKQogIG5ld3B0ID0gIGFwcGx5KGFic05vRXBfbFtbbl1dWyxncmVwbCgiZ2x1dCIsIGNvbG5hbWVzKGFic05vRXBfbFtbbl1dKSldLCAxLAogICAgICAgICAgICAgICAgIG1heCkqKDEtYWJzTm9FcF9sW1tuXV0kZXBlbl9jbHVzXzQpCiAgZmF0ZXMgPSBmYXRlc1shZmF0ZXMgJWluJSBlcGZhdGVzXQogIGZvcihmIGluIGZhdGVzKXsKICAgIHByaW50KGYpCiAgICBzdWJhYnMgPSBhYnNOb0VwX2xbW25dXVssIShjb2xuYW1lcyhhYnNOb0VwX2xbW25dXSk9PWYgfAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb2xuYW1lcyhhYnNOb0VwX2xbW25dXSkgJWluJSBlcGZhdGVzKV0KICAgIHJlbSA9IGlmKCFpcy5udWxsKGRpbShzdWJhYnMpKSl7CiAgICAgIGFwcGx5KHN1YmFicywgMSwgZnVuY3Rpb24oeCkgYW55KHg+PTAuNykpCiAgICB9IGVsc2V7CiAgICAgIGFwcGx5KG1hdHJpeChzdWJhYnMpLCAxLCBmdW5jdGlvbih4KSBhbnkoeD49MC43KSkKICAgIH0KICAgIHRtcFtbZl1dID0gZGF0YS5mcmFtZSgiY2VsbHMiID0gcm93bmFtZXMobWV0YU5vRXBfbFtbbl1dKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAib3JpZ19wdCIgPSBtZXRhTm9FcF9sW1tuXV0kbGF0ZW50X3RpbWUsCiAgICAgICAgICAgICAgICAgICAgICAgICAgIm5ld3B0IiA9IG5ld3B0LAogICAgICAgICAgICAgICAgICAgICAgICAgICJvcmlnX3Byb2IiID0gYWJzTm9FcF9sW1tuXV1bLGZdLAogICAgICAgICAgICAgICAgICAgICAgICAgICJyZWciID0gbWV0YU5vRXBfbFtbbl1dJHJlZ19zaW1wLAogICAgICAgICAgICAgICAgICAgICAgICAgICJjZWxsY2x1c3RlcnMiID0gbWV0YU5vRXBfbFtbbl1dJGNlbGxjbHVzdGVycywKICAgICAgICAgICAgICAgICAgICAgICAgICAiZmF0ZSIgPSBmKQogICAgdG1wW1tmXV0gPSB0bXBbW2ZdXVshcmVtLF0KICAgIHRtcFtbZl1dID0gdG1wW1tmXV1bdG1wW1tmXV0kb3JpZ19wcm9iPj1taW4odG1wW1tmXV0kb3JpZ19wcm9iW3RtcFtbZl1dJG5ld3B0PT0wXSksXQogICAgdG1wW1tmXV0kcHQgPSBzY2FsZXM6OnJlc2NhbGUodG1wW1tmXV0kb3JpZ19wdCwgYygwLDEpKQogICAgdG1wW1tmXV0kcHJvYiA9IHNjYWxlczo6cmVzY2FsZSh0bXBbW2ZdXSRvcmlnX3Byb2IsIGMoMCwxKSkKICB9Cn0KZ2x1dF9kYXRfZGYgPSBSZWR1Y2UocmJpbmQsdG1wKQpgYGAKClNhdmluZyBkYXRhIChmb3IgdXNlIHdpdGggbXVsdGlvbWUpCgpgYGB7cn0KbmV3Y2VsbG5hbWVzID0gZ2x1dF9kYXRfZGYkY2VsbHMKbmV3Y2VsbG5hbWVzID0gZ3N1YigiLWExXzEiLCAiLWExLTEiLCBuZXdjZWxsbmFtZXMpCm5ld2NlbGxuYW1lcyA9IGdzdWIoIi1hMV8yIiwgIi1hMS0yIiwgbmV3Y2VsbG5hbWVzKQpuZXdjZWxsbmFtZXMgPSBnc3ViKCItYTNfMSIsICItYTMtMSIsIG5ld2NlbGxuYW1lcykKbmV3Y2VsbG5hbWVzID0gZ3N1YigiLWEzXzIiLCAiLWEzLTIiLCBuZXdjZWxsbmFtZXMpCmdsdXRfZGF0X2RmJG5ld2NlbGxuYW1lcyA9IG5ld2NlbGxuYW1lcwoKcmVmX2dsdXRfZGF0ID0gZ2x1dF9kYXRfZGZbLGMoMTAsNSwyLDcsNCw2LDMsOCw5KV0KY29sbmFtZXMocmVmX2dsdXRfZGF0KSA9IGMoIm5ld2NlbGxuYW1lcyIsICJyZWdpb24iLCAibGF0ZW50X3RpbWUiLCAiZmF0ZSIsICJwcm9iYWJpbGl0eSIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAiY2VsbGNsdXN0ZXJzIiwgIm5ld19wc2V1ZG90aW1lIiwgIm5vcm1hbGlzZWRfcHNldWRvdGltZSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICJub3JtYWxpc2VkX3Byb2JhYmlsaXR5IikKd3JpdGUuY3N2KHJlZl9nbHV0X2RhdCwgInJlc3VsdHMvUk5BdmVsb2NpdHkvcmVmX2dsdXRfZGF0LmNzdiIsIAogICAgICAgICAgY29sLm5hbWVzID0gVCwgcm93Lm5hbWVzID0gRiwgcXVvdGUgPSBGKQpgYGAKCkNlbGwgdHlwZSBvY2N1cGFuY3kgYnkgYmluLCBwZXIgbGluZWFnZQoKYGBge3J9CmNvbF9wcm9wX2xpc3QgPSBsaXN0KCkKc21vX3Byb3BfbGlzdCA9IGxpc3QoKQpjdF9hbGwgPSBsaXN0KCkKZm9yKG4gaW4gdW5pcXVlKGdsdXRfZGF0X2RmJGZhdGUpKXsKICAjIHN1YnNldCBkYXRhCiAgc3VibWV0YSA9IGdsdXRfZGF0X2RmW2dsdXRfZGF0X2RmJGZhdGU9PW4sXQogIGx0X2JpbnMgPSBjdXQoc3VibWV0YSRuZXdwdCwgMTAwKSAjIDEwMCBlcXVhbGx5LXNpemVkIGJpbnMKICBwbG90X2RmID0gZGF0YS5mcmFtZShiaW5zID0gbHRfYmlucywgCiAgICAgICAgICAgICAgICAgICAgICAgY3N0ID0gYXMuY2hhcmFjdGVyKHN1Ym1ldGEkY2VsbGNsdXN0ZXJzKSkKICB0YWJfZGYgPSB0YWJsZShwbG90X2RmJGJpbnMscGxvdF9kZiRjc3QpCiAgCiAgIyByZW1vdmUgY2VsbCB0eXBlcyB0aGF0IGFyZSB0b28gcmFyZSAoPDUlKQogIHRhYl9kZiA9IHJlc2hhcGUyOjptZWx0KHRhYl9kZi9yb3dTdW1zKHRhYl9kZikpCiAgdXNlY2wgPSB0YXBwbHkodGFiX2RmJHZhbHVlLCB0YWJfZGYkVmFyMiwgZnVuY3Rpb24oeCkgYW55KHg+MC4wNSkpCiAgcGxvdF9kZiA9IHBsb3RfZGZbcGxvdF9kZiRjc3QgJWluJSBuYW1lcyh1c2VjbClbdXNlY2xdLF0KICB0YWJfZGYgPSB0YWJsZShwbG90X2RmJGJpbnMscGxvdF9kZiRjc3QpCiAgCiAgIyBub3JtYWxpc2UgYnkgY2VsbCB0eXBlIGFidW5kYW5jZQogIG1lZF93ID0gcHJvcC50YWJsZSh0YWJsZShwbG90X2RmJGNzdCkpCiAgdGFiX2RmID0gdChhcHBseSh0YWJfZGYsIDEsIGZ1bmN0aW9uKHgpIHgvbWVkX3dbY29sbmFtZXModGFiX2RmKV0pKQogIAogICMgcmVzaGFwZQogIHRhYl9kZiA9IHJlc2hhcGUyOjptZWx0KHRhYl9kZi9yb3dTdW1zKHRhYl9kZikpCiAgdGFiX2RmJFZhcjIgPSBhcy5jaGFyYWN0ZXIodGFiX2RmJFZhcjIpCiAgCiAgIyBwcmV2ZW50IGRpc2NvbnRpbnVpdHkgYnkgY29weWluZyB0aGUgcHJldmlvdXMgY29sdW1uIChsaWtlbHkgbm90IGhhcHBlbmluZykKICB0YWJfZGYgPSB0YWJfZGZbb3JkZXIodGFiX2RmJFZhcjEsIGRlY3JlYXNpbmcgPSBGKSxdCiAgZm9yKGkgaW4gdW5pcXVlKHRhYl9kZiRWYXIxKSl7CiAgICBpZihhbnkoaXMubmFuKHRhYl9kZiR2YWx1ZVt0YWJfZGYkVmFyMT09aV0pKSl7CiAgICAgIHRhYl9kZiR2YWx1ZVt0YWJfZGYkVmFyMT09aV0gPSBwcmV2CiAgICB9CiAgICBwcmV2ID0gdGFiX2RmJHZhbHVlW3RhYl9kZiRWYXIxPT1pXQogIH0KICAKICBjb2xfcHJvcF9saXN0W1tuXV0gPSBnZ3Bsb3QodGFiX2RmLCBhZXMoeCA9IFZhcjEsIHkgPSB2YWx1ZSwgZmlsbCA9IFZhcjIpKSsKICAgIGdlb21fY29sKCkrCiAgICBzY2FsZV95X2NvbnRpbnVvdXMoZXhwYW5kID0gYygwLDApKSsKICAgIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGNvbHNfY2NbbmFtZXMoY29sc19jYykgJWluJSB0YWJfZGYkVmFyMl0pKwogICAgbGFicyh4ID0gIkJpbnMiLCB5ID0gIlByb3BvcnRpb24iLCBmaWxsID0gIkNlbGwgdHlwZSIpKwogICAgdGhlbWVfY2xhc3NpYygpKwogICAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICBheGlzLnRleHQueSA9IGVsZW1lbnRfdGV4dChzaXplID0gNi41LCBjb2xvdXIgPSAiYmxhY2siKSwKICAgICAgICAgIGF4aXMudGlja3MueCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgIGF4aXMubGluZSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgIGF4aXMudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDcpLAogICAgICAgICAgbGVnZW5kLnRleHQgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDYpLAogICAgICAgICAgbGVnZW5kLnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSA3KSwKICAgICAgICAgIGxlZ2VuZC5rZXkuc2l6ZSA9IHVuaXQoMC40LCAiY20iKSkKICAKICAjIHNtb290aGVuIHRoZSBwcm9wb3J0aW9ucyAoYW5kIGZvcmNlIGNvbnN0cmFpbiB0byAwLTEpCiAgdGFiX2RmMiA9IHRhYl9kZgogIHRhYl9kZjIkdmFsdWUyID0gdGFiX2RmMiR2YWx1ZQogIGZvcihpIGluIHVuaXF1ZSh0YWJfZGYyJFZhcjIpKXsKICAgIGZmZiA9IGxvZXNzKHZhbHVlfmFzLm51bWVyaWMoVmFyMSksIGRhdGEgPSB0YWJfZGYyW3RhYl9kZjIkVmFyMj09aSxdLCAKICAgICAgICAgICAgICAgIHNwYW4gPSAwLjUpCiAgICBwcmVkID0gcHJlZGljdChmZmYpCiAgICBwcmVkW3ByZWQ+MV0gPSAxCiAgICBwcmVkW3ByZWQ8MF0gPSAwCiAgICB0YWJfZGYyJHZhbHVlMlt0YWJfZGYyJFZhcjI9PWldID0gcHJlZAogIH0KICAKICAjIGZvcmNlIGNvbnN0cmFpbiBlYWNoIGludGVydmFsIHRvIDAtMSBieSBkb2luZyBwcm9wb3J0aW9uCiAgZm9yKGkgaW4gdW5pcXVlKHRhYl9kZjIkVmFyMSkpewogICAgdGFiX2RmMiR2YWx1ZTJbdGFiX2RmMiRWYXIxPT1pXSA9IHRhYl9kZjIkdmFsdWUyW3RhYl9kZjIkVmFyMT09aV0vc3VtKHRhYl9kZjIkdmFsdWUyW3RhYl9kZjIkVmFyMT09aV0pCiAgfQogIAogIHRhYl9kZjIkbWFqb3IgPSB1bmxpc3QobGFwcGx5KHN0cnNwbGl0KHRhYl9kZjIkVmFyMiwgIl8iKSwgZnVuY3Rpb24oeCkgeFsxXSkpCiAgcmVzID0gbGlzdCgpCiAgZm9yKG5ubiBpbiB1bmlxdWUodGFiX2RmMiRWYXIxKSl7CiAgICBzcz10YXBwbHkodGFiX2RmMlt0YWJfZGYyJFZhcjE9PW5ubiwidmFsdWUyIl0sIHRhYl9kZjJbdGFiX2RmMiRWYXIxPT1ubm4sIm1ham9yIl0sIHN1bSkKICAgIHJlc1tbbm5uXV0gPSB3aGljaC5tYXgoc3MpCiAgfQogIGN0X2FsbFtbbl1dID0gdW5saXN0KGxhcHBseShyZXMsIG5hbWVzKSkKICAKICBzbW9fcHJvcF9saXN0W1tuXV0gPSBnZ3Bsb3QodGFiX2RmMiwgYWVzKHggPSBWYXIxLCB5ID0gdmFsdWUyLCBncm91cCA9IFZhcjIsIGZpbGwgPSBWYXIyKSkrCiAgICBnZW9tX2FyZWEoKSsKICAgIHNjYWxlX3lfY29udGludW91cyhleHBhbmQgPSBjKDAsMCkpKwogICAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gY29sc19jY1tuYW1lcyhjb2xzX2NjKSAlaW4lIHRhYl9kZjIkVmFyMl0pKwogICAgbGFicyh4ID0gIkJpbnMiLCB5ID0gIlByb3BvcnRpb24iLCBmaWxsID0gIkNlbGwgdHlwZSIpKwogICAgdGhlbWVfY2xhc3NpYygpKwogICAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICBheGlzLnRleHQueSA9IGVsZW1lbnRfdGV4dChzaXplID0gNi41LCBjb2xvdXIgPSAiYmxhY2siKSwKICAgICAgICAgIGF4aXMudGlja3MueCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgIGF4aXMubGluZSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgIGF4aXMudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDcpLAogICAgICAgICAgbGVnZW5kLnRleHQgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDYpLAogICAgICAgICAgbGVnZW5kLnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSA3KSwKICAgICAgICAgIGxlZ2VuZC5rZXkuc2l6ZSA9IHVuaXQoMC40LCAiY20iKSkKfQoKZm9yKG4gaW4gbmFtZXMoY29sX3Byb3BfbGlzdCkpewpwZGYocGFzdGUwKCJyZXN1bHRzL1JOQXZlbG9jaXR5L3Byb3BvcnRpb25zL3Byb3BfY2VsbHR5cGVzX3RyYWpfIiwgbiwgIi5wZGYiKSwgCiAgICBoZWlnaHQgPSAyLjYsIHdpZHRoID0gNSkKICBwcmludChjb2xfcHJvcF9saXN0W1tuXV0pCiAgZGV2Lm9mZigpCn0KCmZvcihuIGluIG5hbWVzKGNvbF9wcm9wX2xpc3QpKXsKcGRmKHBhc3RlMCgicmVzdWx0cy9STkF2ZWxvY2l0eS9wcm9wb3J0aW9ucy9wcm9wX2NlbGx0eXBlc190cmFqXyIsIG4sICJfc21vb3RoLnBkZiIpLCAKICAgIGhlaWdodCA9IDIuNiwgd2lkdGggPSA1KQogIHByaW50KHNtb19wcm9wX2xpc3RbW25dXSkKICBkZXYub2ZmKCkKfQpgYGAKCkNlbGwgdHlwZSBvY2N1cGFuY3kgYnkgYmluLCBwZXIgcmVnaW9uCgpgYGB7cn0Kc21vX3Byb3BfbGlzdCA9IGxpc3QoKQpyZXNfYWxsID0gbGlzdCgpICMgZGV0ZXJtaW5lIG1heCBjdCBhdCBlYWNoIHN0ZXAKZm9yKG4gaW4gdW5pcXVlKGdsdXRfZGF0X2RmJHJlZykpewogICMgc3Vic2V0IHJlZ2lvbgogIHN1Ym1ldGEgPSBnbHV0X2RhdF9kZltnbHV0X2RhdF9kZiRyZWc9PW4sXQogIGx0X2JpbnMgPSBjdXQoc3VibWV0YSRuZXdwdCwgMTAwKSAjMTAwIGVxdWFsbHkgc2l6ZWQgYmlucwogIHBsb3RfZGYgPSBkYXRhLmZyYW1lKGJpbnMgPSBsdF9iaW5zLCAKICAgICAgICAgICAgICAgICAgICAgICBjc3QgPSBhcy5jaGFyYWN0ZXIoc3VibWV0YSRjZWxsY2x1c3RlcnMpKQogIHRhYl9kZiA9IHRhYmxlKHBsb3RfZGYkYmlucyxwbG90X2RmJGNzdCkKICAKICAjIHJlbW92ZSBjZWxsIHR5cGVzIHRoYXQgYXJlIHRvbyByYXJlICg8NSUpCiAgdGFiX2RmID0gcmVzaGFwZTI6Om1lbHQodGFiX2RmL3Jvd1N1bXModGFiX2RmKSkKICB1c2VjbCA9IHRhcHBseSh0YWJfZGYkdmFsdWUsIHRhYl9kZiRWYXIyLCBmdW5jdGlvbih4KSBhbnkoeD4wLjA1KSkKICBwbG90X2RmID0gcGxvdF9kZltwbG90X2RmJGNzdCAlaW4lIG5hbWVzKHVzZWNsKVt1c2VjbF0sXQogIHRhYl9kZiA9IHRhYmxlKHBsb3RfZGYkYmlucyxwbG90X2RmJGNzdCkKICAKICAjIG5vcm1hbGlzZSBieSBjZWxsIHR5cGUgYWJ1bmRhbmNlCiAgI21lZF93ID0gcHJvcC50YWJsZSh0YWJsZShwbG90X2RmJGNzdCkpCiAgI3RhYl9kZiA9IHQoYXBwbHkodGFiX2RmLCAxLCBmdW5jdGlvbih4KSB4L21lZF93W2NvbG5hbWVzKHRhYl9kZildKSkKICAjIG5vcm1hbGlzZSBieSBtYWpvciBjZWxsIHR5cGUgYWJ1bmRhbmNlCiAgbWVkX3cgPSBwcm9wLnRhYmxlKHRhYmxlKHVubGlzdChsYXBwbHkoc3Ryc3BsaXQocGxvdF9kZiRjc3QsICJfIiksIGZ1bmN0aW9uKHgpIHhbMV0pKSkpCiAgb3Jjb2wgPSBjb2xuYW1lcyh0YWJfZGYpCiAgbm4gPSB1bmxpc3QobGFwcGx5KHN0cnNwbGl0KGNvbG5hbWVzKHRhYl9kZiksICJfIiksIGZ1bmN0aW9uKHgpIHhbMV0pKQogIHRhYl9kZiA9IHQoYXBwbHkodGFiX2RmLCAxLCBmdW5jdGlvbih4KSB4L21lZF93W25uXSkpCiAgY29sbmFtZXModGFiX2RmKSA9IG9yY29sCiAgCiAgIyByZXNoYXBlCiAgdGFiX2RmID0gcmVzaGFwZTI6Om1lbHQodGFiX2RmL3Jvd1N1bXModGFiX2RmKSkKICB0YWJfZGYkVmFyMiA9IGFzLmNoYXJhY3Rlcih0YWJfZGYkVmFyMikKICAKICAjIHByZXZlbnQgZGlzY29udGludWl0eSBieSBjb3B5aW5nIHRoZSBwcmV2aW91cyBjb2x1bW4gKGxpa2VseSBub3QgaGFwcGVuaW5nKQogIHRhYl9kZiA9IHRhYl9kZltvcmRlcih0YWJfZGYkVmFyMSwgZGVjcmVhc2luZyA9IEYpLF0KICBmb3IoaSBpbiB1bmlxdWUodGFiX2RmJFZhcjEpKXsKICAgIGlmKGFueShpcy5uYW4odGFiX2RmJHZhbHVlW3RhYl9kZiRWYXIxPT1pXSkpKXsKICAgICAgdGFiX2RmJHZhbHVlW3RhYl9kZiRWYXIxPT1pXSA9IHByZXYKICAgIH0KICAgIHByZXYgPSB0YWJfZGYkdmFsdWVbdGFiX2RmJFZhcjE9PWldCiAgfQogIAogICMgc21vb3RoZW4gdGhlIHByb3BvcnRpb25zIChhbmQgZm9yY2UgY29uc3RyYWluIHRvIDAtMSkKICB0YWJfZGYyID0gdGFiX2RmCiAgdGFiX2RmMiR2YWx1ZTIgPSB0YWJfZGYyJHZhbHVlCiAgZm9yKGkgaW4gdW5pcXVlKHRhYl9kZjIkVmFyMikpewogICAgZmZmID0gbG9lc3ModmFsdWV+YXMubnVtZXJpYyhWYXIxKSwgZGF0YSA9IHRhYl9kZjJbdGFiX2RmMiRWYXIyPT1pLF0sIAogICAgICAgICAgICAgICAgc3BhbiA9IDAuNSkKICAgIHByZWQgPSBwcmVkaWN0KGZmZikKICAgIHByZWRbcHJlZD4xXSA9IDEKICAgIHByZWRbcHJlZDwwXSA9IDAKICAgIHRhYl9kZjIkdmFsdWUyW3RhYl9kZjIkVmFyMj09aV0gPSBwcmVkCiAgfQogIAogICMgZm9yY2UgY29uc3RyYWluIGVhY2ggaW50ZXJ2YWwgdG8gMC0xIGJ5IGRvaW5nIHByb3BvcnRpb24KICBmb3IoaSBpbiB1bmlxdWUodGFiX2RmMiRWYXIxKSl7CiAgICB0YWJfZGYyJHZhbHVlMlt0YWJfZGYyJFZhcjE9PWldID0gdGFiX2RmMiR2YWx1ZTJbdGFiX2RmMiRWYXIxPT1pXS9zdW0odGFiX2RmMiR2YWx1ZTJbdGFiX2RmMiRWYXIxPT1pXSkKICB9CiAgCiAgdGFiX2RmMiRtYWpvciA9IHVubGlzdChsYXBwbHkoc3Ryc3BsaXQodGFiX2RmMiRWYXIyLCAiXyIpLCBmdW5jdGlvbih4KSB4WzFdKSkKICByZXMgPSBsaXN0KCkKICBmb3Iobm5uIGluIHVuaXF1ZSh0YWJfZGYyJFZhcjEpKXsKICAgIHNzPXRhcHBseSh0YWJfZGYyW3RhYl9kZjIkVmFyMT09bm5uLCJ2YWx1ZTIiXSwgdGFiX2RmMlt0YWJfZGYyJFZhcjE9PW5ubiwibWFqb3IiXSwgc3VtKQogICAgcmVzW1tubm5dXSA9IHdoaWNoLm1heChzcykKICB9CiAgcmVzX2FsbFtbbl1dID0gdW5saXN0KGxhcHBseShyZXMsIG5hbWVzKSkKICAKICBzbW9fcHJvcF9saXN0W1tuXV0gPSBnZ3Bsb3QodGFiX2RmMiwgYWVzKHggPSBWYXIxLCB5ID0gdmFsdWUyLCBncm91cCA9IFZhcjIsIGZpbGwgPSBWYXIyKSkrCiAgICBnZW9tX2FyZWEoKSsKICAgIHNjYWxlX3lfY29udGludW91cyhleHBhbmQgPSBjKDAsMCkpKwogICAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gY29sc19jY1tuYW1lcyhjb2xzX2NjKSAlaW4lIHRhYl9kZjIkVmFyMl0pKwogICAgbGFicyh4ID0gIkJpbnMiLCB5ID0gIlByb3BvcnRpb24iLCBmaWxsID0gIkNlbGwgdHlwZSIpKwogICAgdGhlbWVfY2xhc3NpYygpKwogICAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICBheGlzLnRleHQueSA9IGVsZW1lbnRfdGV4dChzaXplID0gNi41LCBjb2xvdXIgPSAiYmxhY2siKSwKICAgICAgICAgIGF4aXMudGlja3MueCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgIGF4aXMubGluZSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgIGF4aXMudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDcpLAogICAgICAgICAgbGVnZW5kLnRleHQgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDYpLAogICAgICAgICAgbGVnZW5kLnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSA3KSwKICAgICAgICAgIGxlZ2VuZC5rZXkuc2l6ZSA9IHVuaXQoMC40LCAiY20iKSkKfQoKZm9yKG4gaW4gbmFtZXMoc21vX3Byb3BfbGlzdCkpewpwZGYocGFzdGUwKCJyZXN1bHRzL1JOQXZlbG9jaXR5L3Byb3BvcnRpb25zL3Byb3BfY2VsbHR5cGVzX3RyYWpfcmVnaW9uXyIsIG4sICJfc21vb3RoLnBkZiIpLCAKICAgIGhlaWdodCA9IDIuNiwgd2lkdGggPSA1KQogIHByaW50KHNtb19wcm9wX2xpc3RbW25dXSkKICBkZXYub2ZmKCkKfQpgYGAKCgojIyBWYXJpYWJsZSBnZW5lcwpGaW5kIGFsbCB2YXJpYWJsZSBnZW5lcwoKYGBge3J9CnJlZ2lzdGVyRG9QYXJhbGxlbCg0MCkKCmZpdEV4cCA9IGZ1bmN0aW9uKGcsIG1vZF9kZikgewogIHJlcyA9IGxpc3QoKQogIGNlbGxzID0gbW9kX2RmJGNlbGxzCiAgbW9kX2RmJHkgPSBzY2FsZShleHBfbCRhbGxbY2VsbHMsZ10pCiAgCiAgbSA9IGdhbSh5fmZhdGUqc3BsaW5lczo6bnMobmV3cHQsIGRmID0gNSkrMCwgd2VpZ2h0cyA9IG1vZF9kZiRwcm9iLCBkYXRhID0gbW9kX2RmKQogIHAgPSBtZ2N2OjpwcmVkaWN0LmdhbShtLCBtb2RfZGYsIHR5cGUgPSAibGluayIsIHNlLmZpdCA9IFRSVUUpCiAgZml0c19kZiA9IGRhdGEuZnJhbWUoImZpdCIgPSBwJGZpdCwgInVwX3NlIiA9IHAkZml0KygyKnAkc2UuZml0KSwgImxvX3NlIiA9IHAkZml0LSgyKnAkc2UuZml0KSkKICAKICBiaW5fZGYgPSBkYXRhLmZyYW1lKCJuZXdwdCIgPSByZXAoc2VxKDAsMSxsZW5ndGgub3V0ID0gMTAwKSwgbGVuZ3RoKHVuaXF1ZSh1bmlxdWUobW9kX2RmJGZhdGUpKSkpLAogICAgICAgICAgICAgICAgICAgICAgImZhdGUiID0gcmVwKHVuaXF1ZShtb2RfZGYkZmF0ZSksIGVhY2ggPSAxMDApKQogIHAgPSBwcmVkaWN0KG0sIGJpbl9kZiwgdHlwZSA9ICJsaW5rIiwgc2UuZml0ID0gVFJVRSkKICBiaW5fZGYkZml0ID0gcCRmaXQKICBiaW5fZGYkdXBfc2UgPSBwJGZpdCsoMipwJHNlLmZpdCkKICBiaW5fZGYkbG9fc2UgPSBwJGZpdC0oMipwJHNlLmZpdCkKICAKICByZXNbWyJhbGxfdGVybXMiXV0gPSBsaXN0KCJmaXRzIiA9IGZpdHNfZGYsICJiaW5uZWRfZml0cyIgPSBiaW5fZGYsICJwdmFscyIgPSBzdW1tYXJ5KG0pJHBUZXJtcy5wdikKICAKICAKICBtID0gZ2FtKHl+cmVnKnNwbGluZXM6Om5zKG5ld3B0LCBkZiA9IDUpKzAsIHdlaWdodHMgPSBtb2RfZGYkcHJvYiwgZGF0YSA9IG1vZF9kZikKICBwID0gbWdjdjo6cHJlZGljdC5nYW0obSwgbW9kX2RmLCB0eXBlID0gImxpbmsiLCBzZS5maXQgPSBUUlVFKQogIGZpdHNfZGYgPSBkYXRhLmZyYW1lKCJmaXQiID0gcCRmaXQsICJ1cF9zZSIgPSBwJGZpdCsoMipwJHNlLmZpdCksICJsb19zZSIgPSBwJGZpdC0oMipwJHNlLmZpdCkpCiAgCiAgYmluX2RmID0gZGF0YS5mcmFtZSgibmV3cHQiID0gcmVwKHNlcSgwLDEsbGVuZ3RoLm91dCA9IDEwMCksIGxlbmd0aCh1bmlxdWUodW5pcXVlKG1vZF9kZiRyZWcpKSkpLAogICAgICAgICAgICAgICAgICAgICAgInJlZyIgPSByZXAodW5pcXVlKG1vZF9kZiRyZWcpLCBlYWNoID0gMTAwKSkKICBwID0gbWdjdjo6cHJlZGljdC5nYW0obSwgYmluX2RmLCB0eXBlID0gImxpbmsiLCBzZS5maXQgPSBUUlVFKQogIGJpbl9kZiRmaXQgPSBwJGZpdAogIGJpbl9kZiR1cF9zZSA9IHAkZml0KygyKnAkc2UuZml0KQogIGJpbl9kZiRsb19zZSA9IHAkZml0LSgyKnAkc2UuZml0KQogIAogIHJlc1tbInJlZ2lvbiJdXSA9IGxpc3QoImZpdHMiID0gZml0c19kZiwgImJpbm5lZF9maXRzIiA9IGJpbl9kZiwgInB2YWxzIiA9IHN1bW1hcnkobSkkcFRlcm1zLnB2KQogIAogIHJldHVybihyZXMpCn0KCmZmID0gInJlc3VsdHMvUk5BdmVsb2NpdHkvU1NfZGF0YV9nbHV0Tm9FcF9maXRfZXhwX2V2ZXJ5dGhpbmcuUkRTIgppZihmaWxlLmV4aXN0cyhmZikpewogIGFsbF9maXRfZXhwID0gZm9yZWFjaChpPWNvbG5hbWVzKGV4cF9sJGFsbCkpICVkb3BhciUgewogICAgZml0RXhwKGksIGdsdXRfZGF0X2RmKQogIH0KICBuYW1lcyhhbGxfZml0X2V4cCkgPSBjb2xuYW1lcyhleHBfbCRhbGwpCiAgc2F2ZVJEUyhhbGxfZml0X2V4cCwgZmlsZSA9ICJyZXN1bHRzL1JOQXZlbG9jaXR5L1NTX2RhdGFfZ2x1dE5vRXBfZml0X2V4cF9ldmVyeXRoaW5nLlJEUyIpCn0gZWxzZXsKICBhbGxfZml0X2V4cCA9IHJlYWRSRFMoInJlc3VsdHMvUk5BdmVsb2NpdHkvU1NfZGF0YV9nbHV0Tm9FcF9maXRfZXhwX2V2ZXJ5dGhpbmcuUkRTIikKfQpgYGAKClByZXBhcmUgcHZhbHVlIHRhYmxlcwoKYGBge3J9CnB2YWxfZGYgPSBSZWR1Y2UocmJpbmQsIGxhcHBseShhbGxfZml0X2V4cCwgZnVuY3Rpb24oeCkgeCRhbGxfdGVybXMkcHZhbHMpKQpyb3duYW1lcyhwdmFsX2RmKSA9IG5hbWVzKGFsbF9maXRfZXhwKQoKcHZhbF9yZWdfZGYgPSBSZWR1Y2UocmJpbmQsIGxhcHBseShhbGxfZml0X2V4cCwgZnVuY3Rpb24oeCkgeCRyZWdpb24kcHZhbHMpKQpyb3duYW1lcyhwdmFsX3JlZ19kZikgPSBuYW1lcyhhbGxfZml0X2V4cCkKYGBgCgpQbG90dGluZyBleGFtcGxlIGdlbmVzCgpgYGB7cn0KcGx0R2VuZSA9IGZ1bmN0aW9uKGcsIGRhdCwgbGFiLCBzdWIgPSAiYWxsX3Rlcm1zIil7CiAgcGxvdF9kZiA9IGRhdGEuZnJhbWUoInB0IiA9IGRhdFtyb3duYW1lcyhhbGxfZml0X2V4cFtbZ11dW1tzdWJdXSRmaXRzKSwibmV3cHQiXSwKICAgICAgICAgICAgICAgICAgICAgICAicmVnIiA9IGRhdFtyb3duYW1lcyhhbGxfZml0X2V4cFtbZ11dW1tzdWJdXSRmaXRzKSxsYWJdLAogICAgICAgICAgICAgICAgICAgICAgICJmaXQiID0gYWxsX2ZpdF9leHBbW2ddXVtbc3ViXV0kZml0cyRmaXQsCiAgICAgICAgICAgICAgICAgICAgICAgImZpdF91cCIgPSBhbGxfZml0X2V4cFtbZ11dW1tzdWJdXSRmaXRzJHVwX3NlLAogICAgICAgICAgICAgICAgICAgICAgICJmaXRfZG4iID0gYWxsX2ZpdF9leHBbW2ddXVtbc3ViXV0kZml0cyRsb19zZSkKICAKICBwbHQgPSBnZ3Bsb3QocGxvdF9kZikrCiAgICBnZW9tX2xpbmUobWFwcGluZyA9IGFlcyh4ID0gcHQsIHkgPSBmaXQsIGdyb3VwID0gcmVnLCBjb2xvdXIgPSByZWcpKSsKICAgIGdlb21fcmliYm9uKG1hcHBpbmcgPSBhZXMoeCA9IHB0LCB5ID0gZml0LCBncm91cCA9IHJlZywgZmlsbCA9IHJlZywgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHltaW4gPSBmaXRfZG4sIHltYXggPSBmaXRfdXApLCBhbHBoYSA9IDAuMjUpKwogICAgc2NhbGVfeF9jb250aW51b3VzKGV4cGFuZCA9IGMoMCwwKSkrCiAgICBnZ3RpdGxlKGcpKwogICAgdGhlbWVfY2xhc3NpYygpKwogICAgdGhlbWUoYXNwZWN0LnJhdGlvID0gMSkKICByZXR1cm4ocGx0KQp9Cgpjb3dwbG90OjpwbG90X2dyaWQoCnBsdEdlbmUoIktDTkoxMCIsIGdsdXRfZGF0X2RmLCAiZmF0ZSIpK3RoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIiksCnBsdEdlbmUoIlNPWDYiLCBnbHV0X2RhdF9kZiwgImZhdGUiKSt0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpLApwbHRHZW5lKCJHTEkyIiwgZ2x1dF9kYXRfZGYsICJmYXRlIikrdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKSwKcGx0R2VuZSgiTUVYM0EiLCBnbHV0X2RhdF9kZiwgImZhdGUiKSt0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpLApwbHRHZW5lKCJUT1AyQSIsIGdsdXRfZGF0X2RmLCAiZmF0ZSIpK3RoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIiksCnBsdEdlbmUoIlNMQzE3QTYiLCBnbHV0X2RhdF9kZiwgImZhdGUiKSt0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpLApwbHRHZW5lKCJFTE1PMSIsIGdsdXRfZGF0X2RmLCAiZmF0ZSIpK3RoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIiksCnBsdEdlbmUoIkVPTUVTIiwgZ2x1dF9kYXRfZGYsICJmYXRlIikrdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKSwKbmNvbCA9IDQsIGFsaWduID0gImh2IikKYGBgCgojIyMgVmFyaWFiaWxpdHkgcGVyIHJlZ2lvbgpGaW5kIGdlbmVzIGNvbnNlcnZlZCBhbmQgdmFyaWFibGUgYmV0d2VlbiByZWdpb25zCgpgYGB7cn0KZ19kaWZmID0gcm93bmFtZXMocHZhbF9kZilbcHZhbF9kZlssM108PTAuMDUgJiBwdmFsX3JlZ19kZlssM108PTAuMDVdCnJlZ19ncm91cHMgPSBsaXN0KCJsYXRlcmFsIiA9IGMoImdsdXRfU1VCU0VUXzIiLCAiZ2x1dF9TVUJTRVRfMjIiLCAiZ2x1dF9TVUJTRVRfMTAiKSwgCiAgICAgICAgICAgICAgICAgICJkb3JzYWwiID0gYygiZ2x1dF9TVUJTRVRfMyIsICJnbHV0X1NVQlNFVF8xIiksIAogICAgICAgICAgICAgICAgICAibWVkaWFsIiA9IGMoImdsdXRfU1VCU0VUXzExIiwgImdsdXRfU1VCU0VUXzAiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgImdsdXRfU1VCU0VUXzciLCJnbHV0X1NVQlNFVF8xMyIpKQoKIyBnZXQgdGhlIG1pbmltdW0gY29ycmVsYXRpb24gcGVyIHJlZ2lvbiBhY3Jvc3MgbGluZWFnZXMKY29yX2dfbGlzdCA9IGxpc3QoKQptZWFuX2dfbGlzdCA9IGxpc3QoKQpsaW5fYmlubmVkX2ZpdHMgPSBsaXN0KCkKZm9yKGcgaW4gcm93bmFtZXMocHZhbF9kZikpewogIG1pbl9jb3JzX2cgPSBjKCkKICBtZWFuX2dfZGYgPSBsaXN0KCkKICBmb3IobiBpbiBuYW1lcyhyZWdfZ3JvdXBzKSl7CiAgICBwbG90X2RmID0gYWxsX2ZpdF9leHBbW2ddXSRhbGxfdGVybXMkYmlubmVkX2ZpdHMKICAgIHBsb3RfZGYgPSBwbG90X2RmW3Bsb3RfZGYkZmF0ZSAlaW4lIHJlZ19ncm91cHNbW25dXSxdCiAgICAKICAgIGRhdCA9IGRhdGEuZnJhbWUobGFwcGx5KHJlZ19ncm91cHNbW25dXSwgZnVuY3Rpb24oeCkgcGxvdF9kZiRmaXRbcGxvdF9kZiRmYXRlPT14XSkpCiAgICBjb2xuYW1lcyhkYXQpID0gcmVnX2dyb3Vwc1tbbl1dCiAgICBjYyA9IGNvcihkYXQsIG1ldGhvZCA9ICJzcCIpCiAgICBtaW5fY29yc19nW1tuXV0gPSBtaW4oY2MpCiAgICAKICAgIG1lYW5fZ19kZltbbl1dID0gYXBwbHkoZGF0LCAxLCBtZWFuKQogICAgCiAgICBsaW5fYmlubmVkX2ZpdHNbW2ddXSA9IGlmKG49PW5hbWVzKHJlZ19ncm91cHMpWzFdKXsKICAgICAgZGF0CiAgICB9IGVsc2V7CiAgICAgIGNiaW5kKGxpbl9iaW5uZWRfZml0c1tbZ11dLCBkYXQpCiAgICB9CiAgfQogIGNvcl9nX2xpc3RbW2ddXSA9IG1pbl9jb3JzX2cKICBtZWFuX2dfbGlzdFtbZ11dID0gZGF0YS5mcmFtZShtZWFuX2dfZGYpCn0KY29yX2cgPSBkYXRhLmZyYW1lKFJlZHVjZShyYmluZCwgY29yX2dfbGlzdCkpCnJvd25hbWVzKGNvcl9nKSA9IHJvd25hbWVzKHB2YWxfZGYpCgojIGdldCBnZW5lcyB3aXRoIG5vIHBlci1yZWdpb24gY29ycmVsYXRpb24gbG93ZXIgdGhhbiAwLjMzCmdlbmVzX2FncmVlaW5nX3JlZyA9IGFwcGx5KGFwcGx5KGNvcl9nLCAyLCBmdW5jdGlvbih4KSB4Pj0uMyksIDEsIGZ1bmN0aW9uKHgpIGFsbCh4KSkKCiMgZ2V0IG1pbmltdW0gY29ycmVsYXRpb24gYWNyb3NzIHJlZ2lvbnMKbWluX2Nvcl9yZWdzID0gbGlzdCgpCm1pbl9jb3JfbGlucyA9IGxpc3QoKQpyZWdfYmlubmVkX2ZpdHMgPSBsaXN0KCkKZm9yKGcgaW4gbmFtZXMoZ2VuZXNfYWdyZWVpbmdfcmVnKSl7CiAgcGxvdF9kZiA9IGFsbF9maXRfZXhwW1tnXV0kcmVnaW9uJGJpbm5lZF9maXRzCgogIGRhdCA9IGRhdGEuZnJhbWUobGFwcGx5KHVuaXF1ZShwbG90X2RmJHJlZyksIGZ1bmN0aW9uKHgpIHBsb3RfZGYkZml0W3Bsb3RfZGYkcmVnPT14XSkpCiAgY29sbmFtZXMoZGF0KSA9IHVuaXF1ZShwbG90X2RmJHJlZykKICByZWdfYmlubmVkX2ZpdHNbW2ddXSA9IGRhdAoKICBjYyA9IGNvcihkYXQsIG1ldGhvZCA9ICJzcCIpCiAgbWluX2Nvcl9yZWdzW1tnXV0gPSBtaW4oY2MpCiAgCiAgCiAgcGxvdF9kZiA9IGFsbF9maXRfZXhwW1tnXV0kYWxsX3Rlcm1zJGJpbm5lZF9maXRzCgogIGRhdCA9IGRhdGEuZnJhbWUobGFwcGx5KHVuaXF1ZShwbG90X2RmJGZhdGUpLCBmdW5jdGlvbih4KSBwbG90X2RmJGZpdFtwbG90X2RmJGZhdGU9PXhdKSkKICBjb2xuYW1lcyhkYXQpID0gdW5pcXVlKHBsb3RfZGYkZmF0ZSkKCiAgY2MgPSBjb3IoZGF0LCBtZXRob2QgPSAic3AiKQogIG1pbl9jb3JfbGluc1tbZ11dID0gbWluKGNjKQp9CgojIHByZXBhcmUgdGFibGUgd2l0aCBhbGwgdGhlc2UgcmVzdWx0cwpyZWdpb25fcmVzdWx0X2RhdCA9IGRhdGEuZnJhbWUocm93Lm5hbWVzID0gcm93bmFtZXMocHZhbF9kZiksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiaXNWYXJpYWJsZSIgPSBwdmFsX2RmWywyXTw9MC4wNSAmIHB2YWxfcmVnX2RmWywyXTw9MC4wNSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJwdmFsX3ZhcmlhYmxlTGluZWFnZSIgPSBwdmFsX2RmWywzXSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJwdmFsX3ZhcmlhYmxlUmVnaW9uIiA9IHB2YWxfcmVnX2RmWywzXSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJ2YXJpYWJsZV9saW5lYWdlX2FuZF9yZWdpb24iID0gcHZhbF9kZlssM108PTAuMDUgJgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwdmFsX3JlZ19kZlssM108PTAuMDUsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiYWdyZWVfd2l0aGluX2FsbF9yZWdpb25zIiA9IGdlbmVzX2FncmVlaW5nX3JlZ1tyb3duYW1lcyhwdmFsX2RmKV0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAibWluX2NvcnJfYmV0d2Vlbl9yZWdpb25zIiA9IHVubGlzdChtaW5fY29yX3JlZ3MpW3Jvd25hbWVzKHB2YWxfZGYpXSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJtaW5fY29ycl9iZXR3ZWVuX2xpbmVhZ2VzIiA9IHVubGlzdChtaW5fY29yX2xpbnMpW3Jvd25hbWVzKHB2YWxfZGYpXSkKCnJlZ2lvbl9yZXN1bHRfZGF0JGlzQ29tbW9uUmVnaW9ucyA9IHJlZ2lvbl9yZXN1bHRfZGF0JG1pbl9jb3JyX2JldHdlZW5fcmVnaW9ucz4uMyAmCiAgcmVnaW9uX3Jlc3VsdF9kYXQkaXNWYXJpYWJsZSAmCiAgcmVnaW9uX3Jlc3VsdF9kYXQkYWdyZWVfd2l0aGluX2FsbF9yZWdpb25zCnJlZ2lvbl9yZXN1bHRfZGF0JGlzRGlmZlJlZ2lvbnMgPSByZWdpb25fcmVzdWx0X2RhdCRtaW5fY29ycl9iZXR3ZWVuX3JlZ2lvbnM8KC0uMykKYGBgCgpBZGQgcGVha2luZyB0aW1lcwoKYGBge3J9CiMgYWRkaW5nIG1heCBwb3NpdGlvbiBhY3Jvc3MgbWVhbiBmb3IgYWxsIGdlbmVzCm1lYW5fY29yX2cgPSBsYXBwbHkocm93bmFtZXMocmVnaW9uX3Jlc3VsdF9kYXQpLCBmdW5jdGlvbih4KSBhcHBseShyZWdfYmlubmVkX2ZpdHNbW3hdXSwgMSwgbWVhbikpCm5hbWVzKG1lYW5fY29yX2cpID0gcm93bmFtZXMocmVnaW9uX3Jlc3VsdF9kYXQpCm1lYW5fY29yX2cgPSBkYXRhLmZyYW1lKFJlZHVjZShyYmluZCwgbWVhbl9jb3JfZykpCnJvd25hbWVzKG1lYW5fY29yX2cpID0gcm93bmFtZXMocmVnaW9uX3Jlc3VsdF9kYXQpCm1heF9kYXQgPSBhcHBseShhcHBseShtZWFuX2Nvcl9nLCAxLCBzY2FsZXM6OnJlc2NhbGUsIHRvID0gYygwLDEpKSwgMiwgZnVuY3Rpb24oeCkgd2hpY2gubWF4KHgpKQpyZWdpb25fcmVzdWx0X2RhdCRtYXhQb2ludEFsbFJlZyA9IG1heF9kYXRbcm93bmFtZXMocmVnaW9uX3Jlc3VsdF9kYXQpXQoKIyBtYXggcG9zaXRpb24gcGVyIHJlZ2lvbgptYXhQb2ludFBlclJlZyA9IGxhcHBseShyb3duYW1lcyhyZWdpb25fcmVzdWx0X2RhdCksIAogICAgICAgICAgICAgICAgICAgICAgICBmdW5jdGlvbih4KSBhcHBseShyZWdfYmlubmVkX2ZpdHNbW3hdXSwgMiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZ1bmN0aW9uKHgpIHdoaWNoLm1heChzY2FsZXM6OnJlc2NhbGUoeCwgdG8gPSBjKDAsMSkpKSkpCm1heFBvaW50UGVyUmVnID0gUmVkdWNlKHJiaW5kLCBtYXhQb2ludFBlclJlZykKcm93bmFtZXMobWF4UG9pbnRQZXJSZWcpID0gcm93bmFtZXMocmVnaW9uX3Jlc3VsdF9kYXQpCmNvbG5hbWVzKG1heFBvaW50UGVyUmVnKSA9IHBhc3RlMCgibWF4UG9pbnRQZXJSZWdfIiwgY29sbmFtZXMobWF4UG9pbnRQZXJSZWcpKQpyZWdpb25fcmVzdWx0X2RhdCA9IGNiaW5kKHJlZ2lvbl9yZXN1bHRfZGF0LCBtYXhQb2ludFBlclJlZ1tyb3duYW1lcyhyZWdpb25fcmVzdWx0X2RhdCksXSkKCiMjIGNsYXNzaWZ5IGl0IGludG8gIGVwZW5keW1hbC9ucGMvZ2x1dAphYWEgPSBsYXBwbHkoY29sbmFtZXMobWF4UG9pbnRQZXJSZWcpLCAKICAgICAgICAgICAgIGZ1bmN0aW9uKHgpIHJlc19hbGxbW3N0cnNwbGl0KHgsICJfIilbWzFdXVsyXV1dW21heFBvaW50UGVyUmVnWyx4XV0pCm5hbWVzKGFhYSkgPSBwYXN0ZTAoImN0UG9zaXRpb25fIiwKICAgICAgICAgICAgICAgICAgICB1bmxpc3QobGFwcGx5KHN0cnNwbGl0KGNvbG5hbWVzKG1heFBvaW50UGVyUmVnKSwgIl8iKSwgZnVuY3Rpb24oeCkgeFsyXSkpKQpyZWdpb25fcmVzdWx0X2RhdCA9IGNiaW5kKHJlZ2lvbl9yZXN1bHRfZGF0LCBkYXRhLmZyYW1lKGFhYSkpCgojIG1heCBwZXIgbGluZWFnZQptYXhQb2ludFBlckxpbiA9IGxhcHBseShyb3duYW1lcyhyZWdpb25fcmVzdWx0X2RhdCksIAogICAgICAgICAgICAgICAgICAgICAgICBmdW5jdGlvbih4KSBhcHBseShsaW5fYmlubmVkX2ZpdHNbW3hdXSwgMiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZ1bmN0aW9uKHgpIHdoaWNoLm1heChzY2FsZXM6OnJlc2NhbGUoeCwgdG8gPSBjKDAsMSkpKSkpCm1heFBvaW50UGVyTGluID0gUmVkdWNlKHJiaW5kLCBtYXhQb2ludFBlckxpbikKcm93bmFtZXMobWF4UG9pbnRQZXJMaW4pID0gcm93bmFtZXMocmVnaW9uX3Jlc3VsdF9kYXQpCmNvbG5hbWVzKG1heFBvaW50UGVyTGluKSA9IHBhc3RlMCgibWF4UG9pbnRQZXJMaW5fIiwgY29sbmFtZXMobWF4UG9pbnRQZXJMaW4pKQpyZWdpb25fcmVzdWx0X2RhdCA9IGNiaW5kKHJlZ2lvbl9yZXN1bHRfZGF0LCBtYXhQb2ludFBlckxpbltyb3duYW1lcyhyZWdpb25fcmVzdWx0X2RhdCksXSkKCiMjIGNsYXNzaWZ5IGl0IGludG8gIGVwZW5keW1hbC9ucGMvZ2x1dAphYWEgPSBsYXBwbHkobmFtZXMoY3RfYWxsKSwgCiAgICAgICAgICAgICBmdW5jdGlvbih4KSBjdF9hbGxbW3hdXVttYXhQb2ludFBlckxpblsscGFzdGUwKCJtYXhQb2ludFBlckxpbl8iLCB4KV1dKQpuYW1lcyhhYWEpID0gcGFzdGUwKCJjdFBvc2l0aW9uXyIsIG5hbWVzKGN0X2FsbCkpCnJlZ2lvbl9yZXN1bHRfZGF0ID0gY2JpbmQocmVnaW9uX3Jlc3VsdF9kYXQsIGRhdGEuZnJhbWUoYWFhKSkKYGBgCgpTYXZlIHRhYmxlCgpgYGB7cn0Kd3JpdGUuY3N2KHJlZ2lvbl9yZXN1bHRfZGF0LCBmaWxlID0gInJlc3VsdHMvUk5BdmVsb2NpdHkvcmVnaW9uX2RpZmZlcmVuY2VzX2dsdXQuY3N2IiwgCiAgICAgICAgICByb3cubmFtZXMgPSBULCBjb2wubmFtZXMgPSBULCBxdW90ZSA9IEYpCmBgYAoKQWxsIGdlbmVzIGNvbW1vbiB0byBhbGwgcmVnaW9ucwoKYGBge3J9CiMgZ2V0IGdlbmVzIGNvcnJlbGF0aW5nIGFuZCBub3QgY29ycmVsYXRpbmcgYWNyb3NzIHJlZ2lvbnMKY29yX3JlZyA9IHJvd25hbWVzKHJlZ2lvbl9yZXN1bHRfZGF0KVtyZWdpb25fcmVzdWx0X2RhdCRtaW5fY29ycl9iZXR3ZWVuX3JlZ2lvbnM+LjMgJgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcmVnaW9uX3Jlc3VsdF9kYXQkaXNWYXJpYWJsZSAmCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICByZWdpb25fcmVzdWx0X2RhdCRhZ3JlZV93aXRoaW5fYWxsX3JlZ2lvbnNdCgptZWFuX2Nvcl9nID0gbGFwcGx5KGNvcl9yZWcsIGZ1bmN0aW9uKHgpIGFwcGx5KHJlZ19iaW5uZWRfZml0c1tbeF1dLCAxLCBtZWFuKSkKbmFtZXMobWVhbl9jb3JfZykgPSBjb3JfcmVnCgptZWFuX2Nvcl9nID0gZGF0YS5mcmFtZShSZWR1Y2UocmJpbmQsIG1lYW5fY29yX2cpKQpyb3duYW1lcyhtZWFuX2Nvcl9nKSA9IGNvcl9yZWcKCm1heF9kYXQgPSBhcHBseShhcHBseShtZWFuX2Nvcl9nLCAxLCBzY2FsZXM6OnJlc2NhbGUsIHRvID0gYygwLDEpKSwgMiwgZnVuY3Rpb24oeCkgd2hpY2gubWF4KHgpKQptaW5fZGF0ID0gYXBwbHkoYXBwbHkobWVhbl9jb3JfZywgMSwgc2NhbGVzOjpyZXNjYWxlLCB0byA9IGMoMCwxKSksIDIsIGZ1bmN0aW9uKHgpIHN1bSh4Pi45KSkKc2NfZGF0ID0gb3JkZXIobWF4X2RhdCwgbWluX2RhdCwgZGVjcmVhc2luZyA9IGMoRiwgRikpCgpkYXRfbm9ybSA9IHQoYXBwbHkobWVhbl9jb3JfZ1tzY19kYXQsXSwgMSwgc2NhbGVzOjpyZXNjYWxlLCB0byA9IGMoMCwxKSkpCgpwZGYoInJlc3VsdHMvUk5BdmVsb2NpdHkvaGVhdG1hcF9nZW4vcHJlbWFkZV9wbG90cy9nbHV0Tm9FcF9wYW5yZWdpb25faGVhdG1hcF9hbGwucGRmIiwgCiAgICBoZWlnaHQgPSAyLjI1LCB3aWR0aCA9IDIuNSwgdXNlRGluZ2JhdHMgPSBGKQpwaGVhdG1hcDo6cGhlYXRtYXAoZGF0X25vcm0sIGNsdXN0ZXJpbmdfbWV0aG9kID0gIndhcmQuRCIsIGNvbG9yID0gdmlyaWRpczo6bWFnbWEoMTAwKSwgCiAgICAgICAgICAgICAgICAgICBib3JkZXJfY29sb3IgPSBOQSwgY2x1c3Rlcl9jb2xzID0gRiwgY2x1c3Rlcl9yb3dzID0gRiwKICAgICAgICAgICAgICAgICAgIHNob3dfY29sbmFtZXMgPSBGLCBzaG93X3Jvd25hbWVzID0gRiwgYW5nbGVfY29sID0gMCkKZGV2Lm9mZigpCgp3cml0ZS5jc3YoZGF0X25vcm0sIGZpbGUgPSAicmVzdWx0cy9STkF2ZWxvY2l0eS9oZWF0bWFwX2dlbi9oZWF0bWFwX2RhdF9jb21tb25fcmVnaW9ucy5jc3YiLAogICAgICAgICAgY29sLm5hbWVzID0gVCwgcm93Lm5hbWVzID0gVCwgcXVvdGUgPSBGKQoKZ3JvdXBzX2dlbmVzID0gY3V0KG1heF9kYXQsIDUpCm5hbWVzKGdyb3Vwc19nZW5lcykgPSBuYW1lcyhtYXhfZGF0KQpncm91cHNfZ2VuZXMgPSBzb3J0KGdyb3Vwc19nZW5lcykKCmdvX2wgPSBsaXN0KCkKZm9yKGkgaW4gdW5pcXVlKGdyb3Vwc19nZW5lcykpewogIGdnZyA9IG5hbWVzKGdyb3Vwc19nZW5lcylbZ3JvdXBzX2dlbmVzPT1pXQogIGdvX2xbW2ldXSA9IGdwcm9maWxlcjI6Omdvc3QocXVlcnkgPSBnZ2dbIWdyZXBsKCJBTUVYIiwgZ2dnKV0sIG9yZ2FuaXNtID0gImhzYXBpZW5zIikkcmVzdWx0Cn0KZ2dnID0gbmFtZXMoZ3JvdXBzX2dlbmVzKQpnb19sW1siYWxsIl1dID0gZ3Byb2ZpbGVyMjo6Z29zdChxdWVyeSA9IGdnZ1shZ3JlcGwoIkFNRVgiLCBnZ2cpXSwgb3JnYW5pc20gPSAiaHNhcGllbnMiKSRyZXN1bHQKYGBgCgpURiBhbmQgc2lnbmFsbGluZwoKYGBge3J9Cmh1bWFuX3RmID0gcmVhZC50YWJsZSgiLi4vLi4vZ2VuZV9yZWZzL2h1bWFuL0hvbW9fc2FwaWVuc19URi50eHQiLCBoZWFkZXIgPSBULCBzZXAgPSAiXHQiKQoKcmVzY19tYXQgPSBkYXRfbm9ybVtyb3duYW1lcyhkYXRfbm9ybSkgJWluJSBodW1hbl90ZiRTeW1ib2wsXQptYXhfZGF0ID0gYXBwbHkocmVzY19tYXQsIDEsIGZ1bmN0aW9uKHgpIHdoaWNoLm1heCh4KSkKbWluX2RhdCA9IGFwcGx5KHJlc2NfbWF0LCAxLCBmdW5jdGlvbih4KSBzdW0oeD4uOSkpCnNjX2RhdCA9IG9yZGVyKG1heF9kYXQsIG1pbl9kYXQsIGRlY3JlYXNpbmcgPSBjKEYsIEYpKQoKcGRmKCJyZXN1bHRzL1JOQXZlbG9jaXR5L2hlYXRtYXBfZ2VuL3ByZW1hZGVfcGxvdHMvZ2x1dE5vRXBfcGFucmVnaW9uX2hlYXRtYXBfVEYucGRmIiwgCiAgICBoZWlnaHQgPSAyLjI1LCB3aWR0aCA9IDMsIHVzZURpbmdiYXRzID0gRikKcGhlYXRtYXA6OnBoZWF0bWFwKHJlc2NfbWF0W3NjX2RhdCxdLCBjbHVzdGVyaW5nX21ldGhvZCA9ICJ3YXJkLkQiLCBjb2xvciA9IHZpcmlkaXM6Om1hZ21hKDEwMCksIAogICAgICAgICAgICAgICAgICAgYm9yZGVyX2NvbG9yID0gTkEsIGNsdXN0ZXJfY29scyA9IEYsIGNsdXN0ZXJfcm93cyA9IEYsIGZvbnRzaXplX3JvdyA9IDYuNSwKICAgICAgICAgICAgICAgICAgIHNob3dfY29sbmFtZXMgPSBGLCBzaG93X3Jvd25hbWVzID0gVCwgYW5nbGVfY29sID0gMCkKZGV2Lm9mZigpCnBkZigicmVzdWx0cy9STkF2ZWxvY2l0eS9oZWF0bWFwX2dlbi9wcmVtYWRlX3Bsb3RzL2dsdXROb0VwX3BhbnJlZ2lvbl9oZWF0bWFwX1RGX2xvbmcucGRmIiwgCiAgICBoZWlnaHQgPSA4LjUsIHdpZHRoID0gMywgdXNlRGluZ2JhdHMgPSBGKQpwaGVhdG1hcDo6cGhlYXRtYXAocmVzY19tYXRbc2NfZGF0LF0sIGNsdXN0ZXJpbmdfbWV0aG9kID0gIndhcmQuRCIsIGNvbG9yID0gdmlyaWRpczo6bWFnbWEoMTAwKSwgCiAgICAgICAgICAgICAgICAgICBib3JkZXJfY29sb3IgPSBOQSwgY2x1c3Rlcl9jb2xzID0gRiwgY2x1c3Rlcl9yb3dzID0gRiwgZm9udHNpemVfcm93ID0gNi41LAogICAgICAgICAgICAgICAgICAgc2hvd19jb2xuYW1lcyA9IEYsIHNob3dfcm93bmFtZXMgPSBULCBhbmdsZV9jb2wgPSAwKQpkZXYub2ZmKCkKCgpnbyA9ICJHTzowMDA3MjY3IgpjZWxsY29tbSA9IGdwcm9maWxlcjI6Omdjb252ZXJ0KGdvLCB0YXJnZXQgPSAiSEdOQyIpCmNlbGxjb21tID0gY2VsbGNvbW0kbmFtZVshY2VsbGNvbW0kbmFtZSAlaW4lIGh1bWFuX3RmJFN5bWJvbF0KcmVzY19tYXQgPSBkYXRfbm9ybVtyb3duYW1lcyhkYXRfbm9ybSkgJWluJSBjZWxsY29tbSxdCgptYXhfZGF0ID0gYXBwbHkocmVzY19tYXQsIDEsIGZ1bmN0aW9uKHgpIHdoaWNoLm1heCh4KSkKbWluX2RhdCA9IGFwcGx5KHJlc2NfbWF0LCAxLCBmdW5jdGlvbih4KSBzdW0oeD4uOSkpCnNjX2RhdCA9IG9yZGVyKG1heF9kYXQsIG1pbl9kYXQsIGRlY3JlYXNpbmcgPSBjKEYsIEYpKQoKcGRmKCJyZXN1bHRzL1JOQXZlbG9jaXR5L2hlYXRtYXBfZ2VuL3ByZW1hZGVfcGxvdHMvZ2x1dE5vRXBfcGFucmVnaW9uX2hlYXRtYXBfc2lnbmFsbGluZy5wZGYiLAogICAgaGVpZ2h0ID0gMi4yNSwgd2lkdGggPSAzLCB1c2VEaW5nYmF0cyA9IEYpCnBoZWF0bWFwOjpwaGVhdG1hcChyZXNjX21hdFtzY19kYXQsXSwgY2x1c3RlcmluZ19tZXRob2QgPSAid2FyZC5EIiwgY29sb3IgPSB2aXJpZGlzOjptYWdtYSgxMDApLCAKICAgICAgICAgICAgICAgICAgIGJvcmRlcl9jb2xvciA9IE5BLCBjbHVzdGVyX2NvbHMgPSBGLCBjbHVzdGVyX3Jvd3MgPSBGLAogICAgICAgICAgICAgICAgICAgc2hvd19jb2xuYW1lcyA9IEYsIHNob3dfcm93bmFtZXMgPSBULCBhbmdsZV9jb2wgPSAwKQpkZXYub2ZmKCkKcGRmKCJyZXN1bHRzL1JOQXZlbG9jaXR5L2hlYXRtYXBfZ2VuL3ByZW1hZGVfcGxvdHMvZ2x1dE5vRXBfcGFucmVnaW9uX2hlYXRtYXBfc2lnbmFsbGluZ19sb25nLnBkZiIsCiAgICBoZWlnaHQgPSA5LCB3aWR0aCA9IDMsIHVzZURpbmdiYXRzID0gRikKcGhlYXRtYXA6OnBoZWF0bWFwKHJlc2NfbWF0W3NjX2RhdCxdLCBjbHVzdGVyaW5nX21ldGhvZCA9ICJ3YXJkLkQiLCBjb2xvciA9IHZpcmlkaXM6Om1hZ21hKDEwMCksIAogICAgICAgICAgICAgICAgICAgYm9yZGVyX2NvbG9yID0gTkEsIGNsdXN0ZXJfY29scyA9IEYsIGNsdXN0ZXJfcm93cyA9IEYsIGZvbnRzaXplX3JvdyA9IDYuNSwKICAgICAgICAgICAgICAgICAgIHNob3dfY29sbmFtZXMgPSBGLCBzaG93X3Jvd25hbWVzID0gVCwgYW5nbGVfY29sID0gMCkKZGV2Lm9mZigpCmBgYAoKR2VuZXMgdGhhdCBhcmUgZGlmZmVyZW50IGZvciBlYWNoIHJlZ2lvbiAoYnkgdGhlIHJlZ2lvbiBpbiB3aGljaCB0aGV5IGFyZSByZWxldmFudCkKCmBgYHtyfQpub25jb3JfcmVnID0gcm93bmFtZXMocmVnaW9uX3Jlc3VsdF9kYXQpW3JlZ2lvbl9yZXN1bHRfZGF0JG1pbl9jb3JyX2JldHdlZW5fcmVnaW9uczw9KC0wLjMpXQoKZ19yZWdfZGlmZiA9IGxpc3QoKQpmb3IoZyBpbiBub25jb3JfcmVnKXsKICBoYyA9IGhjbHVzdChkaXN0KHQocmVnX2Jpbm5lZF9maXRzW1tnXV0pKSkKICBjdCA9IHBhc3RlMCgiY2wiLCBjdXRyZWUoaGMsMikpCiAgZ19yZWdfZGlmZltbZ11dID0gaGMkbGFiZWxzW2N0PT1uYW1lcyh0YWJsZShjdCkpW3doaWNoLm1pbih0YWJsZShjdCkpXV0KICAKICAjIGZpbmQgb3V0IGlmIGdlbmUgaXMgYXQgbGVhc3Qgc29tZXdoYXQgdXByZWd1bGF0ZWQgdnMgb3RoZXIgdHdvCiAgYmlubmVkX2ZpdHMgPSByZXNoYXBlMjo6bWVsdChyZWdfYmlubmVkX2ZpdHNbW2ddXSkKICBiaW5uZWRfZml0cyRpc0xhdGVyYWwgPSBiaW5uZWRfZml0cyR2YXJpYWJsZT09ImxhdGVyYWwiCiAgYmlubmVkX2ZpdHMkaXNEb3JzYWwgPSBiaW5uZWRfZml0cyR2YXJpYWJsZT09ImRvcnNhbCIKICBiaW5uZWRfZml0cyRpc01lZGlhbCA9IGJpbm5lZF9maXRzJHZhcmlhYmxlPT0ibWVkaWFsIgogIAogIGFsbGNvZWZmID0gYyhsbSh2YWx1ZX5pc0xhdGVyYWwrMCwgZGF0YSA9IGJpbm5lZF9maXRzKSRjb2VmZmljaWVudHNbMl0sCiAgICAgICAgICAgICAgIGxtKHZhbHVlfmlzRG9yc2FsKzAsIGRhdGEgPSBiaW5uZWRfZml0cykkY29lZmZpY2llbnRzWzJdLAogICAgICAgICAgICAgICBsbSh2YWx1ZX5pc01lZGlhbCswLCBkYXRhID0gYmlubmVkX2ZpdHMpJGNvZWZmaWNpZW50c1syXSkKICBuYW1lcyhhbGxjb2VmZikgPSBjKCJsYXRlcmFsIiwgImRvcnNhbCIsICJtZWRpYWwiKQogIAogIGlmKGFsbGNvZWZmW2dfcmVnX2RpZmZbW2ddXV08MCAmIGFsbChhbGxjb2VmZltuYW1lcyhhbGxjb2VmZikhPWdfcmVnX2RpZmZbW2ddXV0+MCkpewogICAgZ19yZWdfZGlmZltbZ11dID0gbmFtZXMoYWxsY29lZmYpW2FsbGNvZWZmPjBdCiAgfQp9CmRmX3JlZ19kaWZmID0gcmVzaGFwZTI6Om1lbHQoZ19yZWdfZGlmZikKY29sbmFtZXMoZGZfcmVnX2RpZmYpID0gYygicmVnaW9uIiwgImdlbmUiKQoKZ29fcGVyX3JlZyA9IGxpc3QoKQpzb3J0ZWRfZGF0ID0gbGlzdCgpCmZvcihyIGluIHVuaXF1ZShkZl9yZWdfZGlmZiRyZWdpb24pKXsKICBzdWJfZyA9IGRmX3JlZ19kaWZmJGdlbmVbZGZfcmVnX2RpZmYkcmVnaW9uPT1yXQogIG1lYW5fY29yX2cgPSBsYXBwbHkoc3ViX2csIGZ1bmN0aW9uKHgpIHJlZ19iaW5uZWRfZml0c1tbeF1dWyxyXSkKICBuYW1lcyhtZWFuX2Nvcl9nKSA9IHN1Yl9nCiAgCiAgbWVhbl9jb3JfZyA9IGRhdGEuZnJhbWUoUmVkdWNlKHJiaW5kLCBtZWFuX2Nvcl9nKSkKICByb3duYW1lcyhtZWFuX2Nvcl9nKSA9IHN1Yl9nCiAgCiAgbWF4X2RhdCA9IGFwcGx5KGFwcGx5KG1lYW5fY29yX2csIDEsIHNjYWxlczo6cmVzY2FsZSwgdG8gPSBjKDAsMSkpLCAyLCBmdW5jdGlvbih4KSB3aGljaC5tYXgoeCkpCiAgbWluX2RhdCA9IGFwcGx5KGFwcGx5KG1lYW5fY29yX2csIDEsIHNjYWxlczo6cmVzY2FsZSwgdG8gPSBjKDAsMSkpLCAyLCBmdW5jdGlvbih4KSBzdW0oeD4uOSkpCiAgc2NfZGF0ID0gb3JkZXIobWF4X2RhdCwgbWluX2RhdCwgZGVjcmVhc2luZyA9IGMoRiwgRikpCiAgCiAgc29ydGVkX2RhdFtbcl1dID0gdChhcHBseShtZWFuX2Nvcl9nW3NjX2RhdCxdLCAxLCBzY2FsZXM6OnJlc2NhbGUsIHRvID0gYygwLDEpKSkKICAKICBwZGYocGFzdGUwKCJyZXN1bHRzL1JOQXZlbG9jaXR5L2hlYXRtYXBfZ2VuL3ByZW1hZGVfcGxvdHMvZ2x1dE5vRXBfIiwgciwgIl9oZWF0bWFwX2FsbC5wZGYiKSwgCiAgICAgIGhlaWdodCA9IDIuMjUsIHdpZHRoID0gMywgdXNlRGluZ2JhdHMgPSBGKQogIHBoZWF0bWFwOjpwaGVhdG1hcChzb3J0ZWRfZGF0W1tyXV0sIGNsdXN0ZXJpbmdfbWV0aG9kID0gIndhcmQuRCIsIGJvcmRlcl9jb2xvciA9IE5BLAogICAgICAgICAgICAgICAgICAgICBjb2xvciA9IHZpcmlkaXM6Om1hZ21hKDEwMCksIGNsdXN0ZXJfY29scyA9IEYsIGNsdXN0ZXJfcm93cyA9IEYsIAogICAgICAgICAgICAgICAgICAgICBzaG93X2NvbG5hbWVzID0gRiwgc2hvd19yb3duYW1lcyA9IEYpCiAgZGV2Lm9mZigpCiAgCiAgZ3JvdXBzX2dlbmVzID0gY3V0KG1heF9kYXQsIDUpCiAgbmFtZXMoZ3JvdXBzX2dlbmVzKSA9IG5hbWVzKG1heF9kYXQpCiAgZ3JvdXBzX2dlbmVzID0gc29ydChncm91cHNfZ2VuZXMpCiAgCiAgZ29fbCA9IGxpc3QoKQogIGZvcihpIGluIHVuaXF1ZShncm91cHNfZ2VuZXMpKXsKICAgIGdnZyA9IG5hbWVzKGdyb3Vwc19nZW5lcylbZ3JvdXBzX2dlbmVzPT1pXQogICAgZ29fbFtbaV1dID0gZ3Byb2ZpbGVyMjo6Z29zdChxdWVyeSA9IGdnZ1shZ3JlcGwoIkFNRVgiLCBnZ2cpXSwgb3JnYW5pc20gPSAiaHNhcGllbnMiKSRyZXN1bHQKICB9CiAgZ2dnID0gbmFtZXMoZ3JvdXBzX2dlbmVzKQogIGdvX2xbWyJhbGwiXV0gPSBncHJvZmlsZXIyOjpnb3N0KHF1ZXJ5ID0gZ2dnWyFncmVwbCgiQU1FWCIsIGdnZyldLCBvcmdhbmlzbSA9ICJoc2FwaWVucyIpJHJlc3VsdAogIGdvX3Blcl9yZWdbW3JdXSA9IGdvX2wKfQpzYXZlUkRTKHNvcnRlZF9kYXQsIGZpbGUgPSAicmVzdWx0cy9STkF2ZWxvY2l0eS9oZWF0bWFwX2dlbi9yZWdpb25fc3BlY2lmaWNfbWF0X2xpc3QuUkRTIikKYGBgCgpURiBhbmQgc2lnbmFsbGluZwoKYGBge3J9Cmh1bWFuX3RmID0gcmVhZC50YWJsZSgiLi4vLi4vZ2VuZV9yZWZzL2h1bWFuL0hvbW9fc2FwaWVuc19URi50eHQiLCBoZWFkZXIgPSBULCBzZXAgPSAiXHQiKQpmb3IociBpbiBuYW1lcyhzb3J0ZWRfZGF0KSl7CiAgcmVzY19tYXQgPSBzb3J0ZWRfZGF0W1tyXV1bcm93bmFtZXMoc29ydGVkX2RhdFtbcl1dKSAlaW4lIGh1bWFuX3RmJFN5bWJvbCxdCiAgCiAgbWF4X2RhdCA9IGFwcGx5KHJlc2NfbWF0LCAxLCBmdW5jdGlvbih4KSB3aGljaC5tYXgoeCkpCiAgbWluX2RhdCA9IGFwcGx5KHJlc2NfbWF0LCAxLCBmdW5jdGlvbih4KSBzdW0oeD4uOSkpCiAgc2NfZGF0ID0gb3JkZXIobWF4X2RhdCwgbWluX2RhdCwgZGVjcmVhc2luZyA9IGMoRiwgRikpCiAgCiAgcGRmKHBhc3RlMCgicmVzdWx0cy9STkF2ZWxvY2l0eS9oZWF0bWFwX2dlbi9wcmVtYWRlX3Bsb3RzL2dsdXROb0VwXyIsIHIsICJfaGVhdG1hcF9URi5wZGYiKSwgaGVpZ2h0ID0gMi4yNSwgd2lkdGggPSAzLCB1c2VEaW5nYmF0cyA9IEYpCiAgcGhlYXRtYXA6OnBoZWF0bWFwKHJlc2NfbWF0W3NjX2RhdCxdLCBjbHVzdGVyaW5nX21ldGhvZCA9ICJ3YXJkLkQiLCBib3JkZXJfY29sb3IgPSBOQSwKICAgICAgICAgICAgICAgICAgICAgY29sb3IgPSB2aXJpZGlzOjptYWdtYSgxMDApLCBjbHVzdGVyX2NvbHMgPSBGLCBjbHVzdGVyX3Jvd3MgPSBGLCBmb250c2l6ZV9yb3cgPSA3LAogICAgICAgICAgICAgICAgICAgICBzaG93X2NvbG5hbWVzID0gRiwgc2hvd19yb3duYW1lcyA9IFQpCiAgZGV2Lm9mZigpCiAgCiAgcGRmKHBhc3RlMCgicmVzdWx0cy9STkF2ZWxvY2l0eS9oZWF0bWFwX2dlbi9wcmVtYWRlX3Bsb3RzL2dsdXROb0VwXyIsIHIsICJfaGVhdG1hcF9URl9sb25nLnBkZiIpLCBoZWlnaHQgPSA5LCB3aWR0aCA9IDMsIHVzZURpbmdiYXRzID0gRikKICBwaGVhdG1hcDo6cGhlYXRtYXAocmVzY19tYXRbc2NfZGF0LF0sIGNsdXN0ZXJpbmdfbWV0aG9kID0gIndhcmQuRCIsIGJvcmRlcl9jb2xvciA9IE5BLAogICAgICAgICAgICAgICAgICAgICBjb2xvciA9IHZpcmlkaXM6Om1hZ21hKDEwMCksIGNsdXN0ZXJfY29scyA9IEYsIGNsdXN0ZXJfcm93cyA9IEYsIGZvbnRzaXplX3JvdyA9IDcsCiAgICAgICAgICAgICAgICAgICAgIHNob3dfY29sbmFtZXMgPSBGLCBzaG93X3Jvd25hbWVzID0gVCkKICBkZXYub2ZmKCkKfQoKZ28gPSAiR086MDAwNzI2NyIKY2VsbGNvbW0gPSBncHJvZmlsZXIyOjpnY29udmVydChnbywgdGFyZ2V0ID0gIkhHTkMiKQpjZWxsY29tbSA9IGNlbGxjb21tJG5hbWVbIWNlbGxjb21tJG5hbWUgJWluJSBodW1hbl90ZiRTeW1ib2xdCgpzaWdubCA9IHJlYWQuY3N2KCIuLi8uLi9nZW5lX3JlZnMvaHVtYW4vQ2VsbFBob25lREIvZ2VuZV9pbnB1dC5jc3YiLCBoZWFkZXIgPSBUKQpzaWdubCA9IHVuaXF1ZShjKHNpZ25sJGdlbmVfbmFtZVshc2lnbmwkZ2VuZV9uYW1lICVpbiUgaHVtYW5fdGYkU3ltYm9sXSxjZWxsY29tbSkpCgpmb3IociBpbiBuYW1lcyhzb3J0ZWRfZGF0KSl7CiAgcmVzY19tYXQgPSBzb3J0ZWRfZGF0W1tyXV1bcm93bmFtZXMoc29ydGVkX2RhdFtbcl1dKSAlaW4lIHNpZ25sLF0KICAKICBtYXhfZGF0ID0gYXBwbHkocmVzY19tYXQsIDEsIGZ1bmN0aW9uKHgpIHdoaWNoLm1heCh4KSkKICBtaW5fZGF0ID0gYXBwbHkocmVzY19tYXQsIDEsIGZ1bmN0aW9uKHgpIHN1bSh4Pi45KSkKICBzY19kYXQgPSBvcmRlcihtYXhfZGF0LCBtaW5fZGF0LCBkZWNyZWFzaW5nID0gYyhGLCBGKSkKICAKICBwZGYocGFzdGUwKCJyZXN1bHRzL1JOQXZlbG9jaXR5L2hlYXRtYXBfZ2VuL3ByZW1hZGVfcGxvdHMvZ2x1dE5vRXBfIiwgciwKICAgICAgICAgICAgICJfaGVhdG1hcF9zaWduYWxsaW5nLnBkZiIpLCBoZWlnaHQgPSAyLjI1LCB3aWR0aCA9IDMsIHVzZURpbmdiYXRzID0gRikKICBwaGVhdG1hcDo6cGhlYXRtYXAocmVzY19tYXRbc2NfZGF0LF0sIGNsdXN0ZXJpbmdfbWV0aG9kID0gIndhcmQuRCIsIGZvbnRzaXplX3JvdyA9IDcsCiAgICAgICAgICAgICAgICAgICAgIGNvbG9yID0gdmlyaWRpczo6bWFnbWEoMTAwKSwgY2x1c3Rlcl9jb2xzID0gRiwgY2x1c3Rlcl9yb3dzID0gRiwgCiAgICAgICAgICAgICAgICAgICAgIHNob3dfY29sbmFtZXMgPSBGLCBzaG93X3Jvd25hbWVzID0gVCwgYm9yZGVyX2NvbG9yID0gTkEpCiAgZGV2Lm9mZigpCiAgCiAgcGRmKHBhc3RlMCgicmVzdWx0cy9STkF2ZWxvY2l0eS9oZWF0bWFwX2dlbi9wcmVtYWRlX3Bsb3RzL2dsdXROb0VwXyIsIHIsCiAgICAgICAgICAgICAiX2hlYXRtYXBfc2lnbmFsbGluZ19sb25nLnBkZiIpLCBoZWlnaHQgPSAxNywgd2lkdGggPSAzLCB1c2VEaW5nYmF0cyA9IEYpCiAgcGhlYXRtYXA6OnBoZWF0bWFwKHJlc2NfbWF0W3NjX2RhdCxdLCBjbHVzdGVyaW5nX21ldGhvZCA9ICJ3YXJkLkQiLCBmb250c2l6ZV9yb3cgPSA3LAogICAgICAgICAgICAgICAgICAgICBjb2xvciA9IHZpcmlkaXM6Om1hZ21hKDEwMCksIGNsdXN0ZXJfY29scyA9IEYsIGNsdXN0ZXJfcm93cyA9IEYsIAogICAgICAgICAgICAgICAgICAgICBzaG93X2NvbG5hbWVzID0gRiwgc2hvd19yb3duYW1lcyA9IFQsIGJvcmRlcl9jb2xvciA9IE5BKQogIGRldi5vZmYoKQp9CmBgYAoKUGxvdCBpbmRpdmlkdWFsIGdlbmVzCgpgYGB7ciwgZmlnLmhlaWdodD0yLCBmaWcud2lkdGg9NH0KcGx0UmVnR2VuZSA9IGZ1bmN0aW9uKGcsIGRhdCwgY29sLCBncm91cCwgc3ViID0gImFsbF90ZXJtcyIpewogIHBsb3RfZGYgPSBkYXRhLmZyYW1lKCJwdCIgPSBkYXRbcm93bmFtZXMoYWxsX2ZpdF9leHBbW2ddXVtbc3ViXV0kZml0cyksIm5ld3B0Il0sCiAgICAgICAgICAgICAgICAgICAgICAgImNvbCIgPSBkYXRbcm93bmFtZXMoYWxsX2ZpdF9leHBbW2ddXVtbc3ViXV0kZml0cyksY29sXSwKICAgICAgICAgICAgICAgICAgICAgICAiZ3JvdXAiID0gZGF0W3Jvd25hbWVzKGFsbF9maXRfZXhwW1tnXV1bW3N1Yl1dJGZpdHMpLGdyb3VwXSwKICAgICAgICAgICAgICAgICAgICAgICAiZml0IiA9IGFsbF9maXRfZXhwW1tnXV1bW3N1Yl1dJGZpdHMkZml0LAogICAgICAgICAgICAgICAgICAgICAgICJmaXRfdXAiID0gYWxsX2ZpdF9leHBbW2ddXVtbc3ViXV0kZml0cyR1cF9zZSwKICAgICAgICAgICAgICAgICAgICAgICAiZml0X2RuIiA9IGFsbF9maXRfZXhwW1tnXV1bW3N1Yl1dJGZpdHMkbG9fc2UpCiAgCiAgcGx0ID0gZ2dwbG90KHBsb3RfZGYpKwogICAgZ2VvbV9saW5lKG1hcHBpbmcgPSBhZXMoeCA9IHB0LCB5ID0gZml0LCBncm91cCA9IGdyb3VwLCBjb2xvdXIgPSBjb2wpKSsKICAgIGdlb21fcmliYm9uKG1hcHBpbmcgPSBhZXMoeCA9IHB0LCB5ID0gZml0LCBncm91cCA9IGdyb3VwLCBmaWxsID0gY29sLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgeW1pbiA9IGZpdF9kbiwgeW1heCA9IGZpdF91cCksIGFscGhhID0gMC4yNSkrCiAgICBzY2FsZV94X2NvbnRpbnVvdXMoZXhwYW5kID0gYygwLDApKSsKICAgIGdndGl0bGUoZykrCiAgICB0aGVtZV9jbGFzc2ljKCkrCiAgICB0aGVtZShhc3BlY3QucmF0aW8gPSAxLAogICAgICAgICAgYXhpcy50ZXh0ID0gZWxlbWVudF90ZXh0KGNvbG91ciA9ICJibGFjayIsIHNpemUgPSA1KSwKICAgICAgICAgIHRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSA2Ljc1KSkKICByZXR1cm4ocGx0KQp9CgoKZ2VuZXNfdG9fc2hvdyA9IGMoIlRPUDJBIiwgIkdMSTIiLCAiU0xDMTdBNiIsICJORVVST0QyIiwgIkJDTDExQiIsIAogICAgICAgICAgICAgICAgICAiUFJETTIiLCAiRTJGOCIsICJOUkcxIiwgIk5SRzMiLCAiRkdGMTQiLCAiV05UN0IiLCAKICAgICAgICAgICAgICAgICAgIktDTkoxMCIsICJOT1RDSDEiLCAiRkdGUjIiLCAiU0VNQTRGIiwgIk5FVVJMMSIsIAogICAgICAgICAgICAgICAgICAiTUVYM0EiLCAiRU9NRVMiLCAiTkVVUk9ENCIsICJQT1UyRjIiLCAiTUVGMkQiLAogICAgICAgICAgICAgICAgICAiT05FQ1VUMSIsICJGT1hOMyIsICJHTElTMiIsICJaQlRCMTAiLCAiTUVGMkEiLAogICAgICAgICAgICAgICAgICAiWkJUQjQxIiwgIk5GWDEiKQoKbGluZWFnZV9nZW5lcyA9IGxhcHBseShsZF9sLCBmdW5jdGlvbih4KSBsYXBwbHkoY29sbmFtZXMoeClbZ3JlcGwoImNvcnIiLCBjb2xuYW1lcyh4KSldLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZnVuY3Rpb24oeSkgcm93bmFtZXMoeClbb3JkZXIoeFsseV0sIGRlY3JlYXNpbmcgPSBUKV1bMToxMF0pKQoKZ2VuZXNfdG9fc2hvdyA9IHVuaXF1ZShjKGdlbmVzX3RvX3Nob3csIHVubGlzdChsaW5lYWdlX2dlbmVzKSkpCgpwbHRfaW5kX3NoYXJlZCA9IGxpc3QoKQpmb3IoZyBpbiBnZW5lc190b19zaG93KXsKICBmID0gcGFzdGUwKCJyZXN1bHRzL1JOQXZlbG9jaXR5L2luZGl2aWR1YWxfZ2VuZV9raW5ldGljcy9nbHV0Tm9FcF8iLCBnLCAiLnBkZiIpCiAgaWYoIWZpbGUuZXhpc3RzKGYpKXsKICAgIGZfcGx0ID0gcGx0UmVnR2VuZShnLCBnbHV0X2RhdF9kZiwgInJlZyIsICJmYXRlIikrTm9MZWdlbmQoKQogICAgcl9wbHQgPSBwbHRSZWdHZW5lKGcsIGdsdXRfZGF0X2RmLCAicmVnIiwgInJlZyIsICJyZWdpb24iKStOb0xlZ2VuZCgpKwogICAgICB0aGVtZSh0aXRsZSA9IGVsZW1lbnRfYmxhbmsoKSkKICAgIAogICAgcl9wbHQgPSByX3BsdCsKICAgICAgc2NhbGVfY29sb3VyX21hbnVhbCh2YWx1ZXMgPSByZWdfY29sc19zaW1wKSsKICAgICAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gcmVnX2NvbHNfc2ltcCkKICAgIGZfcGx0ID0gZl9wbHQrCiAgICAgIHNjYWxlX2NvbG91cl9tYW51YWwodmFsdWVzID0gcmVnX2NvbHNfc2ltcCkrCiAgICAgIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IHJlZ19jb2xzX3NpbXApCiAgICAgIAogICAgcGx0X2luZF9zaGFyZWRbW2ddXSA9IGNvd3Bsb3Q6OnBsb3RfZ3JpZChmX3BsdCwgcl9wbHQsIG5yb3cgPSAxLCBhbGlnbiA9ICJodiIpCiAgICAKICAgIHBkZihmLCBoZWlnaHQgPSAzLCB3aWR0aCA9IDQuNSkKICAgIHByaW50KHBsdF9pbmRfc2hhcmVkW1tnXV0pCiAgICBkZXYub2ZmKCkKICB9Cn0KYGBgCgoKCmBgYHtyfQpzb3VyY2UoImh0dHBzOi8vZ2l0aHViLmNvbS9xdWFkYmlvbGFiL3ByaW1hdGVfY2VyZWJyYWxfb3JnYW5vaWRzL3Jhdy9tYXN0ZXIvcHRfYWxpZ25tZW50LnIiKQoKeHh4ID0gYWxpZ25fcHRfdHJhaigpCmBgYAoKCgo=